Skip to content

v1.2.0: macOS MEDIUM tier + cmd.exe shim - #2

Merged
ron2k1 merged 13 commits into
mainfrom
macos-port
May 18, 2026
Merged

ron2k1 merged 13 commits into
mainfrom
macos-port

Conversation

@ron2k1

@ron2k1 ron2k1 commented May 16, 2026

Copy link
Copy Markdown
Owner

v1.2.0 — macOS MEDIUM tier + cmd.exe shim

Closes the macOS adoption gap surfaced during outreach: install.sh
previously exit 1'd on Darwin, so every Mac user hit a wall. macOS
now ships a real, honestly-bounded tier, and Windows cmd.exe users
get a shim into the existing STRONG Job Object.

Guarantee model (stated honestly, not papered over)

Platform Primitive Tier
Windows Win32 Job Object + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE STRONG
Windows cmd.exe shim re-execs the PowerShell wrapper, same Job Object STRONG
Linux >= 5.14 cgroup.kill via systemd-run --user --scope + watchdog STRONG
Linux (older / no-systemd / WSL1) setpgid + bash trap MEDIUM
macOS setpgid + trap + disowned out-of-process watchdog MEDIUM

macOS has none of the kernel primitives (no Job Object, no
cgroup.kill, no PR_SET_PDEATHSIG). The MEDIUM tier survives
Force-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.bats as a NEGATIVE test so it cannot
silently regress, and DESIGN.md / README.md state it explicitly.

What's in this branch (11 atomic commits)

  • 14b9c32 macOS runtime pair (find-claude.sh 9-probe with fnm
    dual-dir divergence; claude-jobbed.sh setpgid+trap+watchdog,
    bash-3.2-safe for Apple's frozen /bin/bash)
  • 67df1b6 cmd.exe shim
  • cf5c228 portable mktemp template (BSD/macOS needs explicit arg)
  • e3bc316 real Darwin branch in install.sh (+ ~/.bash_profile
    for Terminal.app's login shell; honest MEDIUM banner)
  • 48a5a45 / 7fb292c / a271071 macOS bats suites (22 tests;
    test-honesty.bats isolated in its own commit because it is the
    load-bearing negative test)
  • 67823df CI: macos-13 + macos-14 legs, plus a /bin/bash -n
    static-parse step under Apple stock 3.2.57 (documents, rather than
    hides, the Homebrew-bash-shadows-/bin/bash runner gap)
  • 2e477c8 / 64e2b71 / e331410 cross-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 Map
    rebuilt to mirror git ls-files exactly (42/42).

Test coverage

  • Windows: 36+1 PowerShell (functional Job Object + orphan classifier
    • config-loader spare-wins invariant + spawn-plan host-routing)
  • POSIX: 40 bats (18 Linux + 22 macOS)
  • CI matrix: ubuntu-latest, macos-13, macos-14, windows-latest

macOS 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

ron2k1 and others added 13 commits May 16, 2026 15:54
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>
@ron2k1
ron2k1 merged commit 65b6548 into main May 18, 2026
6 checks passed
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.

1 participant