Releases: ron2k1/claude-code-structured-concurrency
Release list
v1.2.0 — macOS MEDIUM tier + cmd.exe shim
v1.2.0 — macOS MEDIUM tier + cmd.exe shim
Second cross-platform release, and it closes the roadmap item v1.1.0 promised: macOS support. macOS Claude Code users now get the same subprocess-hygiene story Windows and Linux users have — with an honest MEDIUM ceiling that is pinned by a test so it can never silently regress. cmd.exe users on Windows get the full STRONG guarantee for the first time.
This release also makes the CI matrix honest about hosted Intel macOS, which GitHub is retiring.
What's new
tools/macos/find-claude.sh— macOS probe resolver mirroring the Linux/Windows versions, including the fnm dual-directory layout. Resolves every supported Claude Code install path on Darwin (PATH → npm prefix →/opt/homebrew→/usr/local→ nvm → fnm → asdf → volta → yarn global).tools/macos/claude-jobbed.sh— the macOS reaper:setpgid+ bashtrap+ a disowned, out-of-process watchdog. macOS has none of the kernel primitives the other tiers lean on (no Win32 Job Object, no cgroupcgroup.kill, noprctl(PR_SET_PDEATHSIG)), so the watchdog is what carries the guarantee.tools/claude-jobbed.cmd— cmd.exe shim. It re-execs the PowerShell wrapper and inherits the same STRONG Win32 Job Object. cmd.exe users now get the identical kernel-enforced kill-on-close guarantee PowerShell users have had since v1.0.0 —doskey claude=C:\path\to\tools\claude-jobbed.cmd $*and you are covered.install.sh— real macOS Darwin branch with a portable BSD/macOSmktemptemplate,~/.bash_profilehandling, and an honest-ceiling banner printed at install time so the user is told the MEDIUM limitation up front, not buried in docs.tests/macos/test-honesty.bats— a NEGATIVE test that pins the honest MEDIUM ceiling: it asserts that a simultaneous SIGKILL of wrapper and watchdog leaks by design. If someone ever "fixes" this into a false STRONG claim, this test fails. Honesty is enforced, not promised.- CI matrix — bats now runs on
macos-14(Apple Silicon) alongsideubuntu-latest, with PowerShell onwindows-latest. - Hosted Intel
macos-13dropped, tracked as an open gap (#3). GitHub is retiring hosted Intel macOS; themacos-13leg never received a runner and ran to GitHub's hard 24h "awaiting a runner" ceiling (timeout-minutesbounds execution after a runner is assigned, never queue time). The scripts are architecture-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. Documented as an explicit open gap rather than silently removed, consistent with this project's honest-ceiling ethos.
Guarantee matrix
| Platform | Mechanism | SIGKILL-of-wrapper-alone survival | Simultaneous SIGKILL of wrapper + watchdog |
|---|---|---|---|
| Windows 10+ (PowerShell and cmd.exe) | Win32 Job Object + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
✅ Kernel-enforced | ✅ Kernel-enforced (no watchdog needed) |
Linux ≥ 5.14 + systemd --user |
systemd-run --scope + cgroup.kill (watchdog-supervised) |
✅ Kernel-enforced via cgroup.kill | ✅ Kernel-enforced via cgroup.kill |
| Linux < 5.14 / no systemd / WSL1 | setpgid + bash trap |
❌ (trap doesn't fire on SIGKILL) | ❌ |
| macOS (v1.2.0, MEDIUM) | setpgid + trap + disowned out-of-process watchdog |
✅ Watchdog outlives the wrapper and reaps the tree | ❌ By design — pinned honest by test-honesty.bats |
The v1.1.0 matrix row macOS — deferred to v1.2.0 is now retired: macOS is shipped at the MEDIUM tier above.
Quick start
macOS
curl -fsSL https://raw.githubusercontent.com/ron2k1/claude-code-structured-concurrency/main/install.sh | bash -s -- --yesThe installer prints the honest MEDIUM ceiling banner. Then in a new shell:
claude --version # routes through tools/macos/claude-jobbed.sh transparentlyWindows cmd.exe (new in v1.2.0)
doskey claude=C:\path\to\claude-code-structured-concurrency\tools\claude-jobbed.cmd $*claude from cmd.exe now runs inside the same STRONG Job Object as the PowerShell path.
Windows PowerShell / Linux
Unchanged — see the v1.1.0 release notes.
Why macOS is MEDIUM, not STRONG
Windows has JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; Linux ≥ 5.14 has cgroup.kill. Both are kernel-enforced tree-kill primitives — the OS itself guarantees no descendant outlives the job/scope. macOS has no equivalent: no Job Object, no kill-able cgroup, no PR_SET_PDEATHSIG. The closest honest construction is setpgid + a trap to reap the process group, plus a disowned watchdog (a backgrounded subshell that polls our identity and tears the group down when the wrapper vanishes).
That construction survives a Force-Quit / SIGKILL of the wrapper alone — the watchdog is a separate process, so it outlives the wrapper and reaps the tree. What it cannot survive is a simultaneous SIGKILL of wrapper and watchdog: bash traps don't fire on -9, and there is no kernel backstop on macOS to catch the gap. Rather than paper over that with an optimistic claim, tests/macos/test-honesty.bats asserts the leak happens in that exact scenario — so the ceiling is enforced by CI, not just documented.
Intel (x86_64) macOS coverage — the open gap
CI executes on Apple Silicon (macos-14) only. Hosted Intel (macos-13) execution is intentionally not a CI leg in v1.2.0 (GitHub hosted-Intel retirement; the leg ran to the 24h await-runner ceiling). The scripts are architecture-neutral and bash-3.2-safe by construction, and the static-parse step still proves bash-3.2 syntax — so only x86_64 execution coverage is missing, not syntax correctness. Restoring an Intel execution leg (self-hosted or paid runner) is tracked in #3.
What's next
- v1.3.0+: optional macOS Swift
kqueuehelper to close the simultaneous-SIGKILL gap (would lift macOS toward STRONG); restored Intel macOS execution coverage (#3); Homebrew formula; apt PPA.
Stats
- 36 PowerShell unit assertions + 1 functional test (Windows) + 41 bats tests (19 Linux + 22 macOS) — all passing in CI on
ubuntu-latest,macos-14,windows-latest. - 9 ms reap latency verified on Windows 11 build 26200; macOS/Linux validated in CI (not micro-benched — no synthetic latency numbers claimed for the POSIX tiers).
- macOS honest ceiling pinned by a dedicated negative test (
tests/macos/test-honesty.bats).
v1.1.0 — Linux support
v1.1.0 — Linux support
First cross-platform release. Linux Claude Code users can now get the same subprocess hygiene story Windows users have had since v1.0.0, with a kernel-enforced strong path on modern systems and an honest fallback on older or container-only systems.
What's new
tools/linux/find-claude.sh— 9-probe ordered resolution mirroring the Windows version (PATH → npm prefix →/opt/homebrew→/usr/local→ nvm → fnm → asdf → volta → yarn global). Handles every supported Claude Code install path on Linux + macOS.tools/linux/claude-jobbed.sh— wrapper with two reaper paths picked at runtime:- STRONG:
systemd-run --user --scope(kernel ≥ 5.14, default on Ubuntu 22.04 / Fedora 36 / Debian 12 +). Closest POSIX equivalent of Win32JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. Survives SIGKILL of the wrapper itself via an out-of-process watchdog that triggerscgroup.killon every descendant. - FALLBACK:
setpgid+ bash trap onEXIT/INT/TERM/HUP. Works on no-systemd containers, WSL1, minimal Alpine. Honest caveat: does NOT survive SIGKILL of the wrapper (bash traps don't fire on-9).
- STRONG:
install.sh— top-level OS-detect router.--yes,--force,--uninstallflags. Idempotent shell-function injection in~/.bashrc/~/.zshrc/~/.config/fish/config.fish. Refuses to duplicate without--force.- bats test suite — 19 tests pinning runtime + behavior on
ubuntu-latest. Includes the load-bearingcgroup.killstrong-path test that SIGKILLs the wrapper and asserts the grandchild is reaped within 5s. - CI matrix —
.github/workflows/test.ymlnow runs PowerShell tests onwindows-latestand bats onubuntu-latest(withloginctl enable-lingersosystemctl --useris active for the cgroup test).
Guarantee matrix
| Platform | Strong path | SIGKILL-of-wrapper survival | Fallback |
|---|---|---|---|
| Windows 10+ | Win32 Job Object + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
✅ Kernel-enforced | n/a |
Linux ≥ 5.14 + systemd --user |
systemd-run --scope + cgroup.kill (watchdog-supervised) |
✅ Kernel-enforced via cgroup.kill | setpgid + trap |
| Linux < 5.14 / no systemd / WSL1 | n/a | ❌ (trap doesn't fire on SIGKILL) | setpgid + trap |
| macOS | n/a | ❌ (deferred to v1.2.0) | n/a |
Quick start
Linux
curl -fsSL https://raw.githubusercontent.com/ron2k1/claude-code-structured-concurrency/main/install.sh | bash -s -- --yesThen in a new shell:
claude --version # routes through claude-jobbed.sh transparentlyWindows
Unchanged — see v1.0.3 release notes.
Why the watchdog matters
systemd-run --user --scope is a CONTROLLER, not a parent. Per systemd.scope(5): "When the last process leaves the scope, systemd cleans the scope up." Scope lifetime tracks DESCENDANT lifetime, not controller lifetime. So a SIGKILL of the wrapper just kills systemd-run; the scope stays alive with claude + grandchildren intact. Bash traps don't fire on SIGKILL either, so we cannot tear the scope down from in-process.
The watchdog is a backgrounded subshell that polls our parent identity via stat -c %Y /proc/$$ (the procfs entry mtime equals process start time, which is robust to PID reuse). When it observes the parent vanish, it runs systemctl --user stop $unit, which triggers cgroup.kill on every descendant — delivering the SIGKILL-survival guarantee the README documents.
What's next
- v1.2.0: macOS support (setpgid + trap; honest documentation that Force-Quit case is not covered).
- v1.3.0+: Optional macOS Swift
kqueuehelper for Force-Quit coverage; Homebrew formula; apt PPA.
Stats
- 36 PowerShell tests + 19 bats tests pass on CI
- 3-9 ms reap latency (Windows orphan cleanup)
- ~5 s upper bound for cgroup.kill scope teardown after SIGKILL of wrapper
v1.0.3 -- fix -ShadowClaude wrapper crash on .cmd/.ps1 shims
Bugfix: -ShadowClaude wrapper crash on npm-installed Claude Code
If you ran the v1.0.2 installer with -ShadowClaude and Claude Code was installed via npm, every claude invocation through the wrapper crashed with %1 is not a valid Win32 application. This release fixes that.
Root cause
Start-Process -NoNewWindow requires a true PE binary. npm ships claude as claude.cmd (and claude.ps1) shims, not claude.exe. The wrapper passed the shim path directly to Start-Process, which the OS loader rejects with ERROR_BAD_EXE_FORMAT.
Fix
- New
tools/lib/SpawnPlan.ps1helper -- pure function that maps the resolved claude path to the correct host process:.exe-> direct spawn.cmd/.bat->cmd.exe /c <path>.ps1->powershell.exe -NoProfile -ExecutionPolicy Bypass -File <path>
Find-ClaudeExenow prefers extensions.exe > .cmd > .bat > .ps1when PATH returns multiple shims (the.ps1shim hangs on redirected stdin due to interactive-mode autodetect).- 14 new unit tests in
tests/test-spawn-plan.ps1cover all four extensions plus uppercase / unknown / no-extension fallthrough.
Guarantee unchanged
KILL_ON_JOB_CLOSE still applies. The Job Object is assigned to the host process (cmd.exe / powershell.exe); on Windows 8+ children inherit job membership, so the real node.exe child the shim launches is still kernel-reaped on wrapper exit. Existing tests/test-job-object.ps1 still passes (8ms reap).
Upgrade
cd $env:USERPROFILE\.claude\skills\structured-concurrency
git pull
.\tests\run-all.ps1 # 22+1 tests should passNo re-install needed -- the wrapper script is the file that changed; the -ShadowClaude PROFILE function continues to call it.
v1.0.2 — installer -ShadowClaude flag, README hardening, demo video
First GitHub release. v1.0.0 and v1.0.1 were documentation milestones in the commit log; v1.0.2 is the first cut anyone can git clone --branch v1.0.2 against a stable tag.
What this is
Kernel-enforced cleanup of orphaned Claude Code subprocesses on Windows. Wraps claude.exe in a Win32 Job Object so the OS reaps every child on exit, including crashes. Same kernel mechanism browser sandboxes use to bound renderer and tab lifetime — but no Node.js runtime wires it up for child processes spawned from Claude Code, so this skill does.
Three layers, each composable, each independently tested:
| Tool | Layer | What it does |
|---|---|---|
tools/cc-procs.ps1 |
Visibility | Read-only inventory: PID, parent, age, memory, classification, orphan flag. No kill capability. |
tools/cleanup-orphans.ps1 |
Cleanup | Terminates strict-orphan subtrees per ~/.reap/config.json. Dry-run by default. |
tools/claude-jobbed.ps1 |
Prevention | Win32 Job Object wrapper. Kernel terminates the entire CC tree on wrapper exit. |
Trust and scope
About 642 lines of PowerShell across tools/ and hooks/, plus 345 lines of tests. No network calls, no telemetry, no registry access, no Windows services. The default install is a guaranteed no-op until a ~/.reap/config.json opts in. Uninstall is three commands.
Full per-surface access scope (network, file reads, file writes, registry, process kills, shell profile, claude.exe, background services) with greppable verify commands lives in the README's Auditable section. Threat model, capability boundaries, and vulnerability reporting in SECURITY.md.
What's in v1.0.2
install-reap.ps1 -ShadowClaudeflag — opt-in switch that redefines plainclaudeas a PowerShell function delegating toclaude-jobbed.ps1. Function form (notSet-Alias) because PowerShell resolves Functions before PATH at parse time, so the function wins overclaude.exe. Without the flag, you have to typeclaude-jobbedevery time you want protection.- README rewritten for senior-dev posture — PowerShell-only callout (
cmd.exehas no$PROFILEso the wrapper is bypassed), three-layer component table, explicit safety invariants, prose tightened to match the seriousness of the kernel mechanism it documents. - 58-second demo video at
docs/demo.mp4. Shows the orphan MCP count dropping to zero onclaude.exeexit, with cleanup driven by the kernel's Job Object close — not by application code. - 22 unit assertions plus 1 functional test passing on Windows 11 build 26200. 9 ms measured reap latency for the functional Job Object test.
Why this exists
Claude Code spawns 40-60 child processes per session (MCP servers, plugins, LSPs, hooks). On Windows, those children frequently outlive the parent. The leak class is well-documented in the upstream issue tracker — issue states are noted in brackets so the dossier is self-verifying:
- #42169
[OPEN]— "claude.exe accumulates 13+ GB virtual memory, triggers Windows Resource Exhaustion" - #53134
[OPEN]— "[BUG] Windows: MCP servers spawned twice at startup (directMcpHost + LocalMcpServerManager)" - #28126
[CLOSED · NOT_PLANNED]— "[BUG] Task tool subagents spawn duplicate MCP servers and leak~/.claude/tasks/directories on Windows" - #40667
[CLOSED · DUPLICATE]— "MCP server processes leak on host after subagent/session termination" - #32304
[CLOSED · DUPLICATE]— "[Bug] Memory leak: claude.exe grows to 21GB+ on Windows during normal workflow with sub-agents"
Windows ships the OS-level primitive. Per Microsoft Learn:
A job object allows groups of processes to be managed as a unit. [...] Operations performed on a job object affect all processes associated with the job object.
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE extends that: when the last handle to the job is closed, the kernel terminates every member process. There is no application code path that can leak. Linux has the equivalent in prctl(PR_SET_PDEATHSIG) plus cgroups; macOS has the equivalent semantics. Windows ships the primitive too — Node.js just doesn't wire it up.
This is structured concurrency as Nathaniel J. Smith framed it in 2018: child task lifetimes bounded by their parent, enforced by the runtime, not by application discipline. Application discipline is what produced the leaks in the first place.
Prior art
TheStack-ai/zclean—npx zclean, cross-platform reactive cleanup for AI coding tools (Claude Code, Codex, Cursor). Same problem class, different layer: zclean kills zombies after they form.claude-jobbed.ps1(Layer 3 here) prevents zombies from forming in the first place by binding the process tree to a Win32 Job Object so the kernel reaps on parent exit. The two are complementary — zclean cleans up the past, the Job Object wrapper bounds the future.- Browser sandboxes (Chrome, Edge) use
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEto bound renderer-process lifetime. The mechanism is well-trodden — what's new here is wiring it up for Node-spawned MCP and plugin chains, which no Node.js runtime does on its own.
Install
git clone --branch v1.0.2 https://github.com/ron2k1/claude-code-structured-concurrency `
"$env:USERPROFILE\.claude\skills\structured-concurrency"
& "$env:USERPROFILE\.claude\skills\structured-concurrency\tools\install-reap.ps1" -ShadowClaudeOpen a fresh PowerShell window and confirm:
Get-Command claude
# CommandType=Function (Definition: claude-jobbed @args) -> wrapped
# CommandType=Application -> NOT wrappedVerify
.\tests\test-job-object.ps1 # functional: spawns child, closes job, asserts reaped < 2s
.\tests\test-orphan-detect.ps1 # synthetic: orphan detection + PID-reuse guard via StartTime
.\tests\test-config-loader.ps1 # config: spare-wins-over-kill safety invariantAll three suites must pass before relying on the wrapper. Job Object behavior varies by Windows build; test-job-object.ps1 in particular catches kernel-level edge cases on older builds.
Safety guarantees
cc-procs.ps1never kills. Run it any time.cleanup-orphans.ps1defaults to dry-run. Live kills require both-Forceand a config that opts in. With no~/.reap/config.json, the engine is a guaranteed no-op even with-Force.- The decision flow always runs
spare_classificationsbeforekill_names.claude.exeis classified asclaudeandclaudeis in the defaultspare_classifications, so it cannot be killed even if a user addsnode.exetokill_names. Tested explicitly intests/test-config-loader.ps1. claude-jobbed.ps1is opt-in. Plainclaude.exestill works without the wrapper, just unprotected.
Changelog
| Version | What changed |
|---|---|
v1.0.0 (commit-log only) |
Initial three-layer skill (cc-procs / cleanup-orphans / claude-jobbed) + tests + four config profiles. |
v1.0.1 (commit-log only) |
README adoption-gap warning, 15-question FAQ, dangerous-by-omission framing for the cleanup engine. |
v1.0.2 (this release) |
-ShadowClaude installer flag, README polish + safety-invariant prose, 58-second demo video, SVG architecture diagram, test-config-loader hardening. |
License
MIT. See LICENSE.