Skip to content

ci: run the full suite on macOS, and matrix all three platforms together - #781

Open
noahsabaj wants to merge 4 commits into
PrimeIntellect-ai:mainfrom
noahsabaj:ci-cross-platform
Open

ci: run the full suite on macOS, and matrix all three platforms together#781
noahsabaj wants to merge 4 commits into
PrimeIntellect-ai:mainfrom
noahsabaj:ci-cross-platform

Conversation

@noahsabaj

@noahsabaj noahsabaj commented Aug 6, 2026

Copy link
Copy Markdown

Why

macOS is a supported platform with no CI at all, and Linux and Windows coverage were two
hand-maintained jobs that had already drifted apart: Windows never ran the ai, process-smoke,
or kernel suites, and nothing reported the gap.

Adding macOS surfaced a real bug immediately — the one reported in #669 — which is included
here so the new legs go green rather than red.

What changes

One matrix over (os × suite). A suite added to the list now runs on every platform by
construction rather than by remembering to copy it into a second job. Per-suite settings come
from include; only the steps that genuinely differ — system packages and uv — branch on
runner.os.

The nightly process-stress workflow gets the same three-platform matrix, since it exercises the
process-teardown paths that differ most across platforms.

The macOS fix (fix(macos): keep daemon socket paths inside sun_path)

sockaddr_un.sun_path is 104 bytes on macOS and the BSDs, and a stock macOS $TMPDIR already
spends 49 of them. Measured against the longest name the daemon creates:

platform uid bytes limit result
linux 1000 59 108 unchanged
linux 4294967294 (max) 65 108 unchanged
macOS 501 (default) 102 104 unchanged — two bytes spare
macOS 501234 105 104 overruns
macOS #669's reported path 142 104 overruns

The overrun does not announce itself as "name too long": the name is truncated, so the server
binds one path while clients dial another, and it surfaces 30 seconds later as a daemon
create timeout.

The fallback engages only when the longest socket name would not otherwise fit, so Linux
and the default macOS layout keep byte-identical paths — on Linux it is unreachable at any
possible uid. The fallback directory name includes a hash of the preferred one, so processes
sharing a $TMPDIR agree on it while separate $TMPDIRs stay separate, which keeps an
isolated temporary directory isolating.

Overlaps with #722 and #687, which fix the same report. This variant is here so the macOS
legs added alongside it go green; happy to drop it in favour of either if one lands first. The
difference worth keeping is the hash — a flat /tmp/prime-agent-<uid> collapses two isolated
TMPDIRs into one directory.

Verification

  • Linux Build and check and the Linux suites pass unchanged.
  • macOS legs pass with the sun_path fix; without it, coding-agent 1/3 fails on worker socket
    binding.
  • Windows legs report their real state; they are advisory until the port lands.

Note: full three-platform CI verification was still pending at the time of writing because of
the GitHub Actions incident on 2026-08-06. Re-run before merging.

Note for reviewers

Individual job names change (Build and checkBuild and check (ubuntu-latest), etc.), so any
branch-protection rules pinned to the old names need updating. The aggregate build-check-test
job keeps its name and remains the single required check.


🤖 Generated with Claude Code

Note

Run CI and nightly stress tests across Ubuntu, macOS, and Windows in a matrix

  • Converts build, check, and test jobs in ci.yml and nightly-process-stress.yml to OS matrix jobs across ubuntu-latest, macos-latest, and windows-latest; job timeouts are increased from 15 to 30 minutes.
  • Adds per-OS installation steps for system dependencies (apt, Homebrew, Chocolatey) and uv; uv is installed via Homebrew on macOS and pip --user on Linux/Windows with the appropriate bin directory added to PATH.
  • Fixes defaultDaemonSocketDir in daemon-socket.ts to fall back to a deterministic SHA-256-hashed /tmp subdirectory when the tmpdir-based socket path would exceed the platform's UNIX domain socket path limit (104 bytes on macOS, 108 on Linux).
  • Guards permission-bit and symlink assertions in telemetry.test.ts behind runtime capability probes so tests pass on Windows where these features are not meaningful.
  • Behavioral Change: Windows CI failures are reported but do not gate merges (continue-on-error: true).

Macroscope summarized 577285b.

zhengr pushed a commit to zhengr/prime-agent that referenced this pull request Aug 8, 2026
@SandroHub013

Copy link
Copy Markdown

The matrix is the right place to catch the Windows daemon gaps being reported in #841 and #917. Please include a smoke case where a default named-pipe daemon has no worker descriptor and is still found by status/doctor, plus a process-tree force-stop case. Herdr's Windows endpoint also needs a small integration smoke test: HERDR_SOCKET_PATH is path-like (C:\Users\...\herdr.sock) but the Node endpoint is \\.\pipe\ plus that value.

@noahsabaj

Copy link
Copy Markdown
Author

Thanks — taking the three in turn.

Herdr. The premise does not hold in this tree. herdr-agent-state.ts calls createConnection(socketPath) with the value of HERDR_SOCKET_PATH verbatim; nothing prepends \\.\pipe\. The Windows integration smoke test also already exists: test/herdr-agent-state.test.ts starts the fake herdr server on a named pipe when process.platform === "win32" and points the env var at it. Twelve tests, green on Windows 11 here. If you have a build where the endpoint is composed rather than passed through, please point me at the line — I would like to see it.

The daemon cases. Both are worth having, and I do not think they belong in this PR. This one changes CI wiring only: it adds macOS, folds the three platforms into one (os × suite) matrix, and fixes the uv bootstrap. A no-worker-descriptor status/doctor case and a process-tree force-stop case are new daemon coverage, and reviewing them here would hold up the matrix that has to exist before either can run on Windows at all.

The right home is the port PRs that own those paths — #841 is session-lease recovery and #917 is orphan-tree teardown. Once the matrix lands, any suite added there runs on all three platforms by construction, which is the point of doing it this way round.

noahsabaj and others added 4 commits August 8, 2026 12:28
macOS is a supported platform with no CI at all, and the Linux and Windows
coverage was two hand-maintained jobs that had already drifted: Windows never
ran the `ai`, process-smoke, or kernel suites, and nothing reported that gap.

Replace both with one matrix over (os x suite). A suite added to the list now
runs on every platform by construction rather than by remembering to copy it.
Per-suite settings come from `include`, and only the steps that genuinely
differ -- system packages and uv -- branch on `runner.os`.

macOS takes uv from Homebrew: `pip install --user` puts its scripts under
~/Library/Python/<version>/bin, not ~/.local/bin. Windows asks sysconfig for
the user scripts directory, which carries the interpreter version
(%APPDATA%\Python\Python313\Scripts) -- the fixed %APPDATA%\Python\Scripts it
used before never held uv, so every Windows job needing the Python kernel
failed at bootstrap.

Windows legs run `continue-on-error` for now. They report their real state
without gating the merge, because the native Windows port is still landing in
other PRs; the line comes out once it has.

The nightly process-stress workflow gets the same three-platform matrix, since
it exercises the process-teardown paths that differ most across platforms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adding macOS to CI turns up the failure reported in PrimeIntellect-ai#669 straight away.
`sockaddr_un.sun_path` is 104 bytes on macOS and the BSDs, and a stock macOS
$TMPDIR already spends 49 of them, so the default worker socket path measures
102. A five-digit uid, or a $TMPDIR redirected somewhere deeper by a sandbox or
a test, overruns the field. The overrun does not announce itself as "name too
long": the name is truncated, the server binds one path, clients dial another,
and it surfaces 30 seconds later as a daemon create timeout.

Fall back to a short directory under /tmp, but only when the longest socket
name would not otherwise fit, so Linux and the default macOS layout keep the
exact paths they have today. The fallback name includes a hash of the
preferred directory, so processes sharing a $TMPDIR agree on it while separate
$TMPDIRs stay separate -- an isolated temporary directory keeps isolating.

Overlaps with PrimeIntellect-ai#722 and PrimeIntellect-ai#687, which fix the same report; this variant is here so
the macOS CI legs added alongside it go green. Happy to drop it in favour of
either if one lands first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fallback hashes the preferred directory name, but this branch is cut from
main, where daemon-socket.ts had no reason to import node:crypto. The Windows
port branch does import it for its pipe prefix, which is why the reference
type-checked there and only failed here — I verified the wrong branch.

Caught by `npm run build` on macOS and Linux:
  daemon-socket.ts(249,14): error TS2304: Cannot find name 'createHash'.

Now verified on this branch specifically: npm run build, npm run check, and
daemon-socket 8/8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two assumptions in the new telemetry suite hold on Unix and cannot hold on
Windows, so both cases failed there the moment the file landed.

Windows does not implement POSIX permission bits. A file written with mode
`0o600` reads back as `0o666`, so `statSync().mode & 0o777` cannot observe the
private mode the product asks for. Guard that one assertion rather than skipping
the case, because the identity and persistence assertions around it are worth
running everywhere.

Creating a file symlink needs SeCreateSymbolicLinkPrivilege, which only
Developer Mode or an elevated shell grants, so `symlinkSync` throws EPERM on an
ordinary account. Probe the privilege once and skip the symlink case when it is
absent, matching how `packages/tui/test/autocomplete.test.ts` handles the same
limitation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@noahsabaj

Copy link
Copy Markdown
Author

Rebased onto current main and added one commit, because the analytics work that landed in the meantime brought a test that cannot pass on the Windows legs this PR adds.

test/telemetry.test.ts makes two POSIX-only assumptions:

  • expect(statSync(path).mode & 0o777).toBe(0o600) — Windows does not implement permission bits, so a file written with mode 0o600 reads back as 0o666. The assertion is now guarded rather than the case skipped, since the identity and persistence assertions around it are worth running everywhere.
  • symlinkSync throws EPERM without SeCreateSymbolicLinkPrivilege, which only Developer Mode or an elevated shell grants. The case now probes the privilege once and skips when it is absent, matching how packages/tui/test/autocomplete.test.ts already handles this.

Both fail deterministically on Windows 11 today and pass with the commit. This is the same reason the macOS sun_path fix is in this PR: a matrix that adds a platform has to make that platform's legs report honestly.

Still no CI run on this PR — the checks sit at action_required pending maintainer approval, so build-check-test has never executed. Nothing here can be verified upstream until someone approves the workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants