Claude on Question 14, Oct 2
I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?The short answer
Kill the process group from a trap:
#!/usr/bin/env bash set -euo pipefail trap 'trap - TERM INT; kill -- -$$' EXIT TERM INT sleep 300 & sleep 300 & wait
The trap - TERM INT first disarms the handler so you don't recurse into yourself when the group kill hits your own PID.
The caveat that bites people
kill -- -$$ assumes your script's PID is the process group ID. That's true when bash starts it as a job (interactive shell, or &), but not when it's exec'd or sourced from another non-interactive script — there you inherit the caller's PGID and you'll nuke your parent's siblings.
Make it deterministic by putting yourself in a fresh session:
#!/usr/bin/env bash
set -euo pipefail
if [[ -z ${_RESPAWNED:-} ]]; then
export _RESPAWNED=1
exec setsid --wait "$0" "$@"
fi
trap 'trap - TERM INT; kill -- -$$' EXIT TERM INTNow $$ is guaranteed to be the group leader. Note that setsid detaches from the controlling terminal, so Ctrl-C from the shell no longer reaches you — fine for cron/systemd, annoying interactively.
Alternative if you don't want a new session: set -m enables job control, so each background job gets its own PGID, and you can kill them individually with kill -- -PGID.
Narrower scope: just your own jobs
If you only care about direct children you backgrounded:
cleanup() {
local pids
pids=$(jobs -p)
[[ -n $pids ]] && kill $pids 2>/dev/null
wait 2>/dev/null
}
trap cleanup EXITThis won't reach grandchildren. If a child spawns its own tree, use the process-group approach.
Graceful, then forceful
cleanup() {
trap - TERM INT
kill -TERM -- -$$ 2>/dev/null
for _ in {1..50}; do
kill -0 -- -$$ 2>/dev/null || return
sleep 0.1
done
kill -KILL -- -$$ 2>/dev/null
}When the parent gets SIGKILL
Traps don't run on SIGKILL or power loss — your children survive and get reparented to init. The only real fix is the kernel's parent-death signal, PR_SET_PDEATHSIG:
setpriv --pdeathsig TERM -- long_running_thing &
The kernel signals the child the moment its parent dies, regardless of how. Two things to know:
- •It triggers on the death of the thread that forked, not the whole process — rarely an issue from bash, but relevant if you're wrapping threaded programs.
- •It's cleared across
setuidexec.
For maximum robustness, combine both: pdeathsig on each child as the backstop, trap-based group kill for clean shutdown.
Things that will still escape
- •Double-forking daemons deliberately
setsidthemselves out of your group. You'll need PID files or a cgroup. - •Processes in
Dstate (uninterruptible I/O) ignore even SIGKILL until the syscall returns.
If you need airtight containment, that's what cgroups are for — systemd-run --scope or a transient unit will clean up the entire cgroup on exit, and nothing can escape it.
Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
- Scored
- answered
- Received
- 2026-10-02 09:06:45 UTC
- Run
- 20261002T090008Z-e178a0
- Served by
- Claude Platform on AWS · HTTP 200 · finish stop
- Size
- 2,933 characters · 2,319 tokens out incl. hidden reasoning · 30.4 s
End of response · 90 lines · 2,933 characters · sha256 605494fd610bbc6421c9e6f2b4757be7b67e3ebfe0560440bb6be19540e52b11