feat: native Windows & macOS support (core parity; managed daemon WSL-only)#47
Merged
Conversation
Brainstormed design for native Windows + macOS support. Gates only the managed mock daemon (mock start/stop/status) to POSIX; every other command runs natively on Windows incl. jq-powered features (all heavy deps ship win_amd64 wheels — verified against PyPI). Adds windows/macos to the unit CI matrix. No version bump, no new deps. Co-Authored-By: Claude <noreply@anthropic.com>
Four-task plan from the approved spec: (1) POSIX daemon gate in mock_commands.py, (2) skip the POSIX lifecycle test suite on Windows, (3) expand CI to ubuntu/windows/macOS unit matrix, (4) docs. Audit found the spec's /tmp-test-conversion (D7) has no concrete offenders — recorded as a no-op. Co-Authored-By: Claude <noreply@anthropic.com>
…ause Co-Authored-By: Claude <noreply@anthropic.com>
- config: normalize _load_sample() to LF (CRLF checkout on Windows broke three sample-comparison tests in test_cli.py) - test_mock_commands: skip the real `mock start` test on Windows (gated by _require_posix_daemon); TestDaemonPlatformGate still runs everywhere - test_logs_client: write the follow fixture with newline="" so on-disk bytes match the faked st_size (offset math + \r-free line parsing) - test_discover_command: use Path.as_posix() for the log path embedded in a double-quoted YAML scalar (backslashes are invalid escape sequences) Co-Authored-By: Claude <noreply@anthropic.com>
The config-init/readme tests decoded files with Path.read_text() (platform default = cp1252 on Windows), mojibake-ing the UTF-8 em-dash/arrow in the sample; _load_sample() uses utf-8. Read with encoding="utf-8". The discover test's .as_posix() config path is echoed forward-slash, so assert against log_file.as_posix() (was str(log_file) → backslash mismatch). Co-Authored-By: Claude <noreply@anthropic.com>
mock/daemon.py (is_alive/os.kill, pidfile lifecycle, target resolution) is reached only via the managed daemon commands, which _require_posix_daemon gates on Windows. is_alive() uses os.kill(pid, 0) — TerminateProcess on Windows — which destabilized the whole pytest run (KeyboardInterrupt). Consistent with the test_mock_lifecycle.py Windows skip. Co-Authored-By: Claude <noreply@anthropic.com>
Pre-existing flakes exposed by the Windows matrix (unrelated to the gate): - test_duration_timer_stops_engine: loosen lower bound (>=0.1 -> >=0.05); Windows time.sleep granularity (~15ms) fired a 0.1s timer at 0.093s. - test_concurrency_cap_overflow_returns_429: loosen prompt-429 bound (<delay_ms -> <delay_ms*10); the permit pre-acquire makes rejection deterministic, this is a no-hang sanity check, not a tight timing assert. - test_body_parse_skipped_on_list_body: poll briefly for the http.hit event; the handler thread can append it just after the response is received. Co-Authored-By: Claude <noreply@anthropic.com>
The handler emitted http.unmatched/http.hit AFTER _send_json_response, so a client received the response before the event was appended — a systemic race for any test reading event_sink right after the response (flaked ~1/run on loaded CI cells across all OSes). Emit first: the handler thread runs emit->send in program order, so once the client has the response the event is guaranteed appended (GIL provides the visibility). duration_ms is now measured up to emit (after the delay, before the TCP write) — negligible change. Root-cause fix for the mock_http_server flakes surfaced by the Windows matrix. Co-Authored-By: Claude <noreply@anthropic.com>
- test_mock_http_server: drop the redundant http.hit poll in
test_body_parse_skipped_on_list_body. emit-before-send now guarantees the
event is appended before the response is received (sibling asserts without a
poll); a poll would mask a future regression of the race this branch fixed.
- ARCHITECTURE §15: tighten the graceful-stop wording to match the rest of the
file ("SIGTERM/SIGINT handlers"; only SIGINT fires on native Windows).
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes every agent-facing
agctlcommand run natively on Windows and macOS (including all jq-powered features), gates only the managed mock daemon (mock start/stop/status) to POSIX, and backs the existingOperating System :: OS Independentclassifier with a Windows + macOS CI matrix.What changed
_require_posix_daemon()inagctl/commands/mock_commands.py, wired as the first statement of_mock_start_core/_mock_stop_core/_mock_status_core. Onos.name == "nt"it raises an actionableConfigError(exit 2) pointing atmock runor WSL. WSL reports"posix", so the daemon works there unmodified. Streaming commands (http ping,mock run,logs tail,grpcstreaming) are untouched — their Ctrl+C/SIGINT graceful-stop already works on Windows.osseam, deterministic on every OS); module-levelskipif(os.name == "nt")on the POSIX-only daemon lifecycle suite.ubuntu/windows/macOS × py3.11/3.12/3.13, run commandpytest tests/unit; integration stays ubuntu-only/opt-in.Key technical finding
The suspected blocker — that
jq(themwilliamson/jq.pybinding) has no Windows wheels — is false. Verified against PyPI:jq1.12.0,psycopg-binary3.3.4,grpcio1.82.1, andconfluent-kafka2.15.0 all shipwin_amd64cp311/312/313 wheels. Sopip install 'agctl[kafka,db,grpc,...]'works cleanly on Windows, and jq-powered assertions (--match,--jq-path, kafka--pattern, db--expect-value --path, mockmatch.jq) work natively — nothing is degraded.Out of scope (explicitly)
mock runor WSL.Spec & plan
docs/superpowers/specs/active/2026-07-10-windows-support-design.mddocs/superpowers/plans/active/2026-07-10-windows-support.md(These stay in
active/until the PR merges, then move toarchive/as ADRs.)Windows + macOS CI green is the core-parity proof and runs for the first time on this PR. The local dev host is Linux, so cross-platform correctness is verified by CI, not locally. Please confirm the 9-cell matrix is green before merging.
🤖 Generated with Claude Code