Skip to content

v1.0.2 — installer -ShadowClaude flag, README hardening, demo video

Choose a tag to compare

@ron2k1 ron2k1 released this 08 May 04:12

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 -ShadowClaude flag — opt-in switch that redefines plain claude as a PowerShell function delegating to claude-jobbed.ps1. Function form (not Set-Alias) because PowerShell resolves Functions before PATH at parse time, so the function wins over claude.exe. Without the flag, you have to type claude-jobbed every time you want protection.
  • README rewritten for senior-dev posture — PowerShell-only callout (cmd.exe has no $PROFILE so 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 on claude.exe exit, 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/zcleannpx 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_CLOSE to 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" -ShadowClaude

Open a fresh PowerShell window and confirm:

Get-Command claude
# CommandType=Function (Definition: claude-jobbed @args)  ->  wrapped
# CommandType=Application                                  ->  NOT wrapped

Verify

.\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 invariant

All 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.ps1 never kills. Run it any time.
  • cleanup-orphans.ps1 defaults to dry-run. Live kills require both -Force and 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_classifications before kill_names. claude.exe is classified as claude and claude is in the default spare_classifications, so it cannot be killed even if a user adds node.exe to kill_names. Tested explicitly in tests/test-config-loader.ps1.
  • claude-jobbed.ps1 is opt-in. Plain claude.exe still 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.