●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
I Stopped the Headless Claude Code Job, but the Build Kept Running — Designing Teardown Around exit 143
In headless mode, a SIGTERM received while Claude Code was mid-Bash used to orphan the command's process tree. The 2.1.212 fix changes that to a clean exit 143. Here is how to redesign your supervisor and cleanup around that contract.
As an indie developer, I was running a nightly maintenance job for one of my apps and stopped it by hand one morning. I typed systemctl stop, the prompt returned immediately, and I moved on. A few hours later a disk-usage alert fired on the VM. When I looked, a single next build process that the job had spawned was still alive, holding onto the CPU. Its parent was gone, but the child kept running. It was still holding a port, too, so the next scheduled session failed to bind and quietly did nothing.
The stop command had succeeded. What had failed was the stop reaching the child. This article walks through what actually happened when a headless (-p / SDK) Claude Code process was stopped mid-Bash, how Claude Code 2.1.212 changed it, and how to rebuild your supervisor and cleanup around the new exit 143 contract — in the order I hit each piece.
The job I stopped kept eating CPU
First, what exactly was orphaned. A headless Claude Code process launches a shell as a child whenever it calls the Bash tool. That shell launches npm run build, and the build launches its own workers. The tree gets deep.
Layer
Process
Role
Parent
claude (headless)
Runs the turn, calls tools
Child
bash -c
Shell for the tool call
Grandchild
npm / node
The actual build or test
Great-grandchild
worker threads
Where the CPU actually goes
What systemctl stop and timeout send by default is a single SIGTERM to the parent. The parent claude receives it and tries to wind the turn down. The problem: if the parent exits before explicitly ending the child tree, the grandchildren lose their parent, get reparented to init (PID 1) as orphans, and keep running. The build does not stop, the port is not released, and the temporary intermediate files keep filling the disk.
That is exactly what I hit, and the nasty part is that the stop command's exit code was 0. From the supervisor's point of view, it had worked.
Why orphaning happens — signals do not reach a process group automatically
This part is not specific to Claude Code; it is plain Unix, but it is the heart of the problem. kill <pid> and a default SIGTERM reach only the single PID you name. They are not automatically propagated to descendants.
There are two ways to propagate. One is to send to the whole process group with kill -TERM -<pgid> (a leading minus makes the number a process group ID). The other is for the parent itself to walk its descendants inside its SIGTERM handler and end them before exiting.
Before the fix, headless Claude Code was missing that "clean up descendants before exiting" step for the case where it was stopped mid-Bash. The parent shut down politely while the grandchildren were left behind. So in any environment where the supervisor was not holding KillMode itself, orphans slipped right through.
How you send it
What it reaches
Grandchildren
kill -TERM <pid>
Only that PID
May be left behind
kill -TERM -<pgid>
The whole group
Reached together
Parent kills descendants in handler
What the parent walks
Depends on the parent
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Why SIGTERM does not reach a process group on its own, and the interrupt / tree-kill / exit 143 contract introduced in 2.1.212
✦Before/After code that rewrites the timeout and systemd supervisor around exit 143
✦A verification step to confirm no orphans remain, and how the work splits with SessionEnd hooks
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Claude Code 2.1.212 fixes this. When a print/SDK-mode process receives SIGTERM while a Bash command is running, Claude Code now interrupts the in-flight turn, kills the process tree of the command it launched, and exits with 143.
That number is meaningful. On Unix, a process terminated by a signal reports an exit code of 128 + signal number. SIGTERM is signal 15, so 128 + 15 = 143. In other words, exit 143 is now a clear signal that says "I received SIGTERM, respected it, and wound down." It is no longer the ambiguous state where the parent quietly exits 0 while a grandchild lives on.
For automation this is a small change with a large effect. After you send the stop signal, what the supervisor needs to confirm shifts from "is the exit code 0?" to "is the exit code 143, and are there no descendants left?" Because the contract is now explicit, your own design can be written just as explicitly.
Before / After — rewriting the supervisor around exit 143
Here is the minimal supervisor for running a headless job under timeout. The old shape did not account for orphans at all.
Before (a shape that drops orphans):
#!/usr/bin/env bash# A supervisor that only stops the job after a time limittimeout 30m claude -p "$(cat nightly_task.md)" \ --output-format json > run.logecho "exit=$?"# timeout sends SIGTERM to the parent only, by default.# Even if a grandchild build survives, this line is reached and prints exit
timeout 30m sends SIGTERM to the parent at the time limit, and that is all. If a grandchild survives after claude exits, the script marches on as if nothing happened.
After (fold the whole group, and interpret exit 143):
#!/usr/bin/env bashset -uo pipefailLOG=run.log# setsid creates a new process group so the whole job is one group.# timeout sends TERM via --signal and, after a grace period, KILL via --kill-after.setsid timeout --signal=TERM --kill-after=20s 30m \ claude -p "$(cat nightly_task.md)" --output-format json > "$LOG" &JOB_PGID=$!# If the supervisor itself is stopped, send TERM to the whole job groupcleanup() { # leading minus = propagate to the process group kill -TERM -"$JOB_PGID" 2>/dev/null || true # after a grace period, KILL anything that remains sleep 20 kill -KILL -"$JOB_PGID" 2>/dev/null || true}trap cleanup TERM INTwait "$JOB_PGID"CODE=$?case "$CODE" in 0) echo "done: clean exit" ;; 143) echo "stopped: honored SIGTERM and wound down (expected)" ;; 124) echo "warning: timeout reached, force-stopped (work incomplete)" ;; *) echo "abnormal: exit=$CODE" ;;esac
Three things changed. setsid groups the job into its own process group; the stop signal is sent with a leading minus, kill -TERM -"$JOB_PGID", so it reaches the whole group; and exit code 143 is branched explicitly as an "expected, graceful stop." Because 2.1.212 makes Claude Code kill the child tree itself, --kill-after stays as a safety net while the real teardown can be left to Claude Code.
When you run it under systemd
If you run the job as a long-lived or scheduled systemd service, control how the stop is delivered at the unit level. If that layer is loose, it can undo the 2.1.212 fix from above.
[Service]Type=simpleExecStart=/usr/local/bin/nightly-claude.sh# Send SIGTERM on stop (default). Let Claude Code wind down politely firstKillSignal=SIGTERM# Target the whole control group so grandchildren are reachedKillMode=control-group# Grace period on stop; long enough to let Claude Code kill its treeTimeoutStopSec=45# Do not treat 143 as a failure (it is a respected stop, so count it as success)SuccessExitStatus=143
KillMode=control-group is the key line. systemd manages the service as a cgroup, so this delivers the signal to every process in the cgroup on stop. Even if Claude Code's behavior changes later, you keep the supervisor layer in full control of the whole tree. SuccessExitStatus=143 keeps a graceful stop from being misread as a failure and paging you awake at 3 a.m.
Confirm, after the fact, whether orphans were escaping
Rather than trusting "it should be fixed now," it is worth confirming in your own environment that no orphans remain. Here is a simple check that counts the PIDs in the job's process group before and after you send the stop signal.
#!/usr/bin/env bash# Launch the job and capture its PGIDsetsid claude -p "$(cat probe_task.md)" > /dev/null 2>&1 &PGID=$!sleep 8 # wait for the build etc. to spawn grandchildrenecho "processes in group before stop:"pgrep -g "$PGID" | wc -l# stop it politelykill -TERM -"$PGID"sleep 5 # wait for the tree kill to completeREMAIN=$(pgrep -g "$PGID" | wc -l)echo "remaining processes 5s after stop: $REMAIN"if [ "$REMAIN" -eq 0 ]; then echo "OK: no orphans left"else echo "investigate: $REMAIN still alive. Check the supervisor's KillMode" pgrep -g "$PGID" -afi
I run this check once whenever I update Claude Code. Watching the count settle to 0 five seconds after the stop tells me it is safe to tighten --kill-after in the supervisor or TimeoutStopSec in systemd. Being able to confirm it with a number breaks the habit of padding the grace period out of guesswork.
What you still clean up yourself — the split with SessionEnd hooks
Killing the process tree only goes as far as "stop the running processes." The side effects those processes held — a half-written lock file, a partially built artifact, a recorded port reservation — do not disappear just because the process is gone. That part is yours to fold up.
The 2.1.212 fix turned a headless Claude Code stop from an ambiguous 0 into an explicit exit 143. It interrupts, kills the tree, and honors the signal. Because the contract is now clear, the supervisor can be written just as simply: 143 is expected, anything else needs a look.
If you run headless Claude Code overnight or on a schedule, the next step is just one thing. Send a single manual SIGTERM to a running job and, five seconds later, check with pgrep -g whether any process remains. If it is 0, your teardown reaches the descendants. If something is left, there is still a gap to close in the supervisor's KillMode or grace settings.
I have had a single surviving orphan hold a port and make the following night's job miss entirely. The mechanism that stops your work deserves as much design as the mechanism that runs it. Thank you for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.