v1.2.0: macOS MEDIUM tier + cmd.exe shim - #2
Merged
Merged
Conversation
macOS has none of the three strong primitives the other ports use (no Job Object, no cgroup.kill, no PR_SET_PDEATHSIG) and no /proc. A plain setpgid+trap wrapper is only WEAK there: bash traps never fire on SIGKILL, so Activity Monitor "Force Quit" of the wrapper orphans the tree. Port the out-of-process watchdog from the Linux STRONG path (cfe7479) to reach MEDIUM: a disowned subshell polls the wrapper's identity and reaps the child process group the moment the wrapper vanishes. Being a separate process, it survives a SIGKILL aimed at the wrapper alone -- the WEAK -> MEDIUM upgrade. Two macOS-specific adaptations from the Linux template: - parent identity is `ps -p $pid -o lstart=` (no /proc/$$ mtime); trimmed identically on both sides so the compare can't skew. - bash 3.2-safe throughout: stock /usr/bin/env bash is Apple's frozen 3.2.57. No arrays, no [[ =~ ]], no ${x,,}, no mapfile. The watchdog reuses cleanup() (inherited by the subshell fork) so the in-process and watchdog kill paths cannot drift. `set -m` is load-bearing twice: the child gets its own pgid (killable as -$child_pgid without hitting the wrapper) and the watchdog lands in a different pgid (so the group kill can't take it down). find-claude.sh mirrors the Linux 9-probe contract exactly; the one divergence is probe 6 checking both fnm layouts (~/.local/share on Linux, ~/Library/Application Support on macOS). Honest ceiling: a simultaneous kill -9 of BOTH wrapper and watchdog still leaks -- macOS has no kernel primitive to cover that. MEDIUM, not STRONG. tests/macos/test-honesty.bats will pin this. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd.exe has no $PROFILE and cannot call the Win32 API, so it can neither auto-shadow `claude` nor host a Job Object itself. Rather than ship a weaker cmd-native tier, re-exec into PowerShell running the existing claude-jobbed.ps1 so a cmd.exe user gets the IDENTICAL STRONG KILL_ON_JOB_CLOSE guarantee with zero second code path to keep in sync. %~dp0 resolves the sibling .ps1 regardless of caller cwd; the -NoProfile -ExecutionPolicy Bypass -File invocation matches the v1.0.3 SpawnPlan .ps1 host-routing convention; exit /b %ERRORLEVEL% propagates the real exit code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
inject_into and uninstall_from both created their scratch file with a
bare `mktemp`. GNU coreutils mktemp defaults a template when given no
argument, so this worked on Linux. BSD mktemp (macOS) requires an
explicit template and exits non-zero with a usage error otherwise --
which would break --force reinstall and --uninstall the moment the
Darwin branch (next commit) lets those code paths run on a Mac.
Use mktemp "${TMPDIR:-/tmp}/csc-block.XXXXXX": GNU accepts the explicit
template too, so this is a strict portability improvement with zero
behavior change on Linux.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Darwin previously exited 1 with a "v1.2.0 roadmap" message. Replace it
with a working install path:
- platform=macos; the guarantee string states the MEDIUM tier in full
-- setpgid + out-of-process watchdog, survives Force-Quit / kill -9
of the wrapper alone, NOT survivable if wrapper AND watchdog are
kill -9'd simultaneously. macOS has no cgroup.kill or Job Object
equivalent; that is the honest ceiling, stated as such, not a TODO.
- inject_all / uninstall_all helpers as a single source of truth so
the install and uninstall rc-file target lists can never drift.
macOS Terminal.app runs bash as a LOGIN shell (reads ~/.bash_profile,
not ~/.bashrc), so macOS additionally targets ~/.bash_profile.
- platform-aware rc_human banner so the pre-flight ack lists the files
actually touched on this OS.
Pairs with tools/macos/{find-claude,claude-jobbed}.sh (14b9c32) and
the cmd.exe shim (67df1b6). Depends on the portable mktemp from
cf5c228 so --force and --uninstall work on BSD/macOS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Faithful mirrors of the green tests/linux/*.bats, adapted for the three
REAL macOS divergences (not cosmetic forks):
- find-claude: probe 6 must check BOTH fnm layouts -- the XDG
~/.local/share/fnm AND the macOS-default
"~/Library/Application Support/fnm". Added a dedicated 6b test the
Linux suite has no reason to carry.
- pgid-cleanup: NO CLAUDE_JOBBED_FORCE_FALLBACK toggle exists on
macOS (one setpgid + trap + out-of-process watchdog path, always
armed), so the suite drives that single path instead of the Linux
dual-path.
- installer: ~/.bash_profile IS targeted (Terminal.app runs bash as a
LOGIN shell) and the banner must state the honest MEDIUM ceiling in
words -- both asserted, neither meaningful on Linux.
All three skip with an honest reason off-macOS (BSD mktemp templates,
Darwin-only install.sh arm) rather than a green that proves nothing.
20 bats across these 3 suites; the load-bearing negative test
(test-honesty.bats) lands next, isolated for git-log visibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Isolated in its own commit on purpose: this is the load-bearing
NEGATIVE test and `git log` should make that visible. It is the only
file in the suite that asserts a leak STILL happens.
CASE 1 (proves we beat WEAK): SIGKILL of the wrapper ALONE -- the
disowned out-of-process watchdog notices and reaps the child group.
A plain setpgid+trap wrapper would leak here (bash traps never fire
on SIGKILL). Asserts the grandchild DIES.
CASE 2 (pins the ceiling, does NOT claim STRONG): a SIMULTANEOUS
SIGKILL of wrapper AND watchdog leaks the tree. macOS has no
cgroup.kill / Job Object. Asserts the grandchild SURVIVES.
If someone "fixes" CASE 2 into a leak-free pass they either added a real
kernel primitive (update the guarantee) or wrote something that does not
hold -- the test comment says exactly that, so the diff must explain
itself. The install banner and DESIGN.md state this ceiling in prose;
this test is what keeps those words true.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tests/linux/test-installer.bats skipped off-Linux with the message "install.sh is Linux-only in v1.1.0 -- macOS lands in v1.2.0". macOS support ships in THIS branch, so that string now lies about project state to anyone reading a skipped-test report or the source. Guard logic is deliberately unchanged and still correct: these assertions encode the LINUX rc-file contract (targets ~/.bashrc + ~/.zshrc, deliberately never ~/.bash_profile -- the macOS path differs and has its own suite). Only the comment and skip message are corrected, now pointing at tests/macos/test-installer.bats. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both architectures on purpose -- macos-14 is Apple Silicon (arm64), macos-13 is Intel (x86_64). The reaped surface is process-group + signal + ps behavior, which can differ by arch; fail-fast off so an arch-only regression still surfaces the other arch's result. Mirrors the linux job's "probe -> verify environment honestly -> run suite" shape, plus a macOS-specific wrinkle: GitHub runners put a modern Homebrew bash ahead of Apple's frozen /bin/bash 3.2.57 on PATH, so the functional bats suite may NOT exercise the 3.2 constraint the wrapper is written for. Rather than hide that, a dedicated step parses the runtime scripts under `/bin/bash -n` (Apple stock 3.2.57) and the probe logs which bash runs the suite. Same "never silently pass" ethos as the linux job's systemd --user gate. bats-core (not the dead pre-1.0 `bats` formula) via brew. on.push.branches already carries macos-port; no trigger change needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The README still described macOS as "WEAK -- planned", cmd.exe as "not supported", and carried a "Help on macOS yet" non-goal -- all false now that v1.2.0 ships the macOS MEDIUM tier (tools/macos/) and the tools/claude-jobbed.cmd delegating shim. Stale aspirational language in a shipped recruiting artifact reads as neglect; replaced it with a precisely-bounded MEDIUM claim that names its own pinning test (tests/macos/test-honesty.bats). Sweep covers: Platform/Tests badges (40 bats), guarantee-matrix macOS row WEAK->MEDIUM with the honest simultaneous-SIGKILL ceiling, IMPORTANT block (cmd.exe now supported via shim, not "PowerShell only"), Linux->Linux/macOS install section with the .bash_profile login-shell rationale, Components cmd + macOS tables, a new "How it works / macOS" walkthrough, the macOS bats table, and the CI section with the honest Homebrew-bash-shadows-/bin/bash gap note. Verify-before-done also caught three cross-file drift items not in the original edit plan: Requirements had no macOS bullet (only Windows/Linux), test-find-claude.bats still said Homebrew probes "land in v1.2.0 macOS CI" (now shipped, not future), and "either platform" implied two when there are three. Fixed all three. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DESIGN.md was frozen at "Version: 1.0.0" and predated both the Linux v1.1.0 and macOS v1.2.0 ports. Most of it was merely stale, but line 26 was actively FALSE: it listed "Running on macOS or Linux" as a non-goal on the claim that those platforms "already have OS-level reapers ... equivalent semantics on macOS." That is the exact inverse of reality -- macOS has no Job Object, no cgroup.kill, and no PR_SET_PDEATHSIG, and that absence is the entire reason the macOS tier tops out at MEDIUM. In a recruiting artifact a stale line reads as neglect; a confidently-wrong technical claim reads as not understanding your own system, which is worse. Replaced it with an honest macOS-STRONG-gap non-goal and added a "> Note:" paragraph that explicitly owns the prior error (self-auditing spec is a credibility signal, not a liability). Also in this sweep: - Header -> Version 1.2.0 + Status line (Windows STRONG / Linux STRONG+MEDIUM / macOS MEDIUM + cmd.exe) + Revised 2026-05-16. - Problem statement generalized to the cross-platform process chain (Windows cmd.exe->npx.cmd->node vs POSIX sh->npx->node). - New "Cross-platform guarantee matrix" section + "Why macOS is MEDIUM, not STRONG" rationale (setpgid + trap + disowned out-of-process watchdog; honest simultaneous-SIGKILL ceiling; Linux v1.1.0 watchdog-architecture reuse). - Verification: added test-spawn-plan.ps1 (item 4) and the bats suites (item 5), naming test-cgroup-kill.bats and test-honesty.bats as the two load-bearing negative tests; status line -> 36+1 pwsh + 40 bats, full CI matrix. - WSL open question struck through (resolved in v1.1.0). - Versioning section rewritten to the actual shipped releases (v1.0.0 / v1.0.2-3 / v1.1.0 / v1.2.0 / Deferred). Architecture / Why-Job-Objects / Component-contracts bodies left unchanged on purpose -- still accurate for Windows, and a pre-release rewrite of correct prose is needless risk (Rule #9). Commit 2 of 3 in the v1.2.0 docs sweep (1: README 2e477c8). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The skill's `description` is the classifier input that decides
whether the skill loads at all -- and it was Windows-scoped
("structured concurrency on Windows", Win32-only triggers, no
macOS/Linux/cmd terms). That is the precise mechanism by which a
Mac user's "Claude Code keeps leaking node processes on my mac"
never reached this skill: the macOS code/tests/CI shipped in
v1.2.0 are useless if retrieval never surfaces them. Rewrote the
description cross-platform and deliberately pushy against
undertriggering (explicit Mac/cmd.exe/Linux phrasings, "activity
monitor full of node"), since undertriggering for Mac users is
the exact bug v1.2.0 exists to fix.
Also de-Windowsed the title; rewrote the Overview's
Linux-and-Windows-only-primitive passage (it implied only Windows
was wired up and omitted that macOS has NO kernel primitive --
the SKILL.md analogue of the false DESIGN.md line-26) into the
honest per-platform STRONG/MEDIUM guarantee, naming
test-honesty.bats as the macOS-ceiling pin; tagged the
Diagnostic/Cleanup layers Windows-only (only the prevention
wrapper + find-claude were ported, per the actual code) and made
Prevention the named cross-platform layer; added a Platform
dispatch block (cmd.exe shim, install.sh, POSIX wrappers); fixed
the verify step (4 PS suites + the bats matrix, not 3 PS files);
and added a Rule #1 honesty note that macOS/Linux are
CI-validated, NOT locally micro-benched (no synthetic latency
numbers claimed for the POSIX tiers).
REBUILT the File Map from `git ls-files` ground truth -- the old
map omitted install.sh, the entire tools/linux + tools/macos
trees, claude-jobbed.cmd, lib/SpawnPlan.ps1, all 8 bats files,
test-spawn-plan.ps1, SECURITY.md, docs/FAQ.md, and
.github/workflows. A File Map that does not match `git ls-files`
is exactly what a senior reviewer greps to spot-check rigor in a
recruiting artifact.
Verify-before-done (re-read full file, same pass that caught 3
README drifts) caught one cross-file drift NOT in the edit plan:
the "When to Use" trigger list -- a GATING section -- was still
Windows-only symptoms (Task Manager / Windows Command Processor /
node.exe), so a Mac user who did reach the body would conclude it
is Windows-only and undo the description fix. Generalized it to
Activity Monitor / `ps`/`htop` / PID-1 reparented `node`.
Commit 3 of 3 in the v1.2.0 docs sweep (1: README 2e477c8,
2: DESIGN 64e2b71). SKILL.md house style is ASCII `--`/`->`/`>=`,
matched (distinct from README/DESIGN em-dash).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The macos-13 matrix leg never once received a runner. Both runs that included it (push 25973920036, PR 25973931445) sat in "awaiting a runner" until GitHub's hard 24h account ceiling and were auto-cancelled, keeping the `tests` check permanently non-green and blocking PR #2. Root cause is not code. macos-14 -- same job definition, only the matrix os value differs -- is green in ~25s, as are ubuntu-latest and windows-latest. GitHub is retiring hosted Intel macOS, so macos-13 is unschedulable for this account. Critically, `timeout-minutes: 15` bounds execution time AFTER a runner is assigned and does nothing for queue starvation -- which is why a "15-minute" job died at 24h, not 15 min. No workflow knob can cap "awaiting a runner". Matrix reduced to [macos-14]. Kept the strategy/matrix mechanism single-entry (not collapsed to a bare runs-on) so re-adding an Intel leg later is a one-line change and the green check keeps its `bats (macos-14)` name. The scripts are architecture-neutral and bash-3.2-safe by construction; the static-parse step still proves the 3.2 syntax constraint, so only x86_64 execution is uncovered. Intel execution coverage is tracked as an explicit open gap, not silently abandoned: #3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reconciles every macos-13 reference across README, DESIGN, and SKILL to match the matrix change in 290de96 -- in lockstep, so the three surfaces never disagree mid-history. - README: macOS CI is the macos-14 leg; an explicit clause states Intel is intentionally not a CI leg (GitHub hosted-Intel retirement, 24h await-runner) with a link to #3; "macOS legs" -> "macOS leg". - DESIGN: the CI-matrix line and the v1.2.0 Versioning bullet drop macos-13 and name #3; a new "Open questions (deferred)" bullet tracks Intel execution coverage as an open gap, next to the WSL entry -- the same honest-open-gap posture chosen over silent removal. - SKILL: the verify-step leg list, the File Map CI-matrix annotation, and the perf-validation line all drop macos-13 and point at #3. Test COUNTS are unchanged (22 macOS bats still run, on macos-14) -- only the matrix-leg enumeration moved. Honest framing throughout: the scripts are arch-neutral and bash-3.2-safe by construction and the static-parse step still proves the 3.2 syntax, so only x86_64 execution is uncovered, not syntax. Co-Authored-By: Claude Opus 4.7 (1M context) <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.
v1.2.0 — macOS MEDIUM tier + cmd.exe shim
Closes the macOS adoption gap surfaced during outreach:
install.shpreviously
exit 1'd on Darwin, so every Mac user hit a wall. macOSnow ships a real, honestly-bounded tier, and Windows
cmd.exeusersget a shim into the existing STRONG Job Object.
Guarantee model (stated honestly, not papered over)
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEcgroup.killviasystemd-run --user --scope+ watchdogsetpgid+ bashtrapsetpgid+trap+ disowned out-of-process watchdogmacOS has none of the kernel primitives (no Job Object, no
cgroup.kill, noPR_SET_PDEATHSIG). The MEDIUM tier survivesForce-Quit / SIGKILL of the wrapper alone (the watchdog outlives it
and reaps the tree) but NOT a simultaneous SIGKILL of wrapper and
watchdog. That ceiling is not hidden — it is pinned by
tests/macos/test-honesty.batsas a NEGATIVE test so it cannotsilently regress, and DESIGN.md / README.md state it explicitly.
What's in this branch (11 atomic commits)
14b9c32macOS runtime pair (find-claude.sh9-probe with fnmdual-dir divergence;
claude-jobbed.shsetpgid+trap+watchdog,bash-3.2-safe for Apple's frozen /bin/bash)
67df1b6cmd.exe shimcf5c228portablemktemptemplate (BSD/macOS needs explicit arg)e3bc316real Darwin branch ininstall.sh(+~/.bash_profilefor Terminal.app's login shell; honest MEDIUM banner)
48a5a45/7fb292c/a271071macOS bats suites (22 tests;test-honesty.batsisolated in its own commit because it is theload-bearing negative test)
67823dfCI:macos-13+macos-14legs, plus a/bin/bash -nstatic-parse step under Apple stock 3.2.57 (documents, rather than
hides, the Homebrew-bash-shadows-/bin/bash runner gap)
2e477c8/64e2b71/e331410cross-platform docs sweep(README / DESIGN / SKILL). DESIGN's prior macOS non-goal was
actively false (claimed macOS already had equivalent reapers) —
replaced and explicitly owned via a
> Note:. SKILL.md File Maprebuilt to mirror
git ls-filesexactly (42/42).Test coverage
ubuntu-latest,macos-13,macos-14,windows-latestmacOS is not locally bench-able from the Windows dev box (WSL
firmware-blocked); the macos-13 / macos-14 CI legs are the proof,
same implement -> push -> CI -> self-heal pattern as the v1.1.0
Linux port (PR #1).
🤖 Generated with Claude Code