Claude on Question 14, Sep 25
I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?A few layers here, depending on how robust you need to be.
The common idiom: trap + tracked PIDs
#!/usr/bin/env bash
set -euo pipefail
pids=()
cleanup() {
trap - INT TERM EXIT # avoid re-entry
[[ ${#pids[@]} -gt 0 ]] && kill "${pids[@]}" 2>/dev/null
wait # reap
}
trap cleanup INT TERM EXIT
worker1 & pids+=($!)
worker2 & pids+=($!)
waitNote the explicit INT TERM — bash does not run an EXIT trap when it's killed by an untrapped signal, so trap cleanup EXIT alone leaves you leaking on kill <script>.
Process groups: catches grandchildren too
Tracked PIDs miss anything your children spawned. Killing the whole process group fixes that:
#!/usr/bin/env bash
set -euo pipefail
set -m # job control: make this shell a pgrp leader
cleanup() {
trap - INT TERM EXIT
kill -TERM -$$ 2>/dev/null # negative PID = process group
sleep 0.2
kill -KILL -$$ 2>/dev/null
}
trap cleanup INT TERM EXITThe set -m matters: without it a script run from a non-interactive context may share its parent's process group, and kill -- -$$ either fails or nukes something you didn't intend. Verify with ps -o pid,pgid,cmd.
The real problem: SIGKILL
Traps don't run on SIGKILL, and they don't run if the parent segfaults or the box loses power mid-run. If you need children to die no matter how the parent went, you need the kernel to enforce it.
PR_SET_PDEATHSIG — the child gets a signal when its parent exits. On util-linux ≥ 2.33:
setpriv --pdeathsig TERM -- long_running_thing &
Caveat: it fires when the thread that forked exits, and it's cleared across setuid exec. Also, if the child is itself a shell that forks, only the direct child gets signalled.
cgroups / systemd scope — the strongest option, since escape is impossible:
systemd-run --user --scope --collect -- ./my_script.sh
Everything in the scope is torn down together. --collect cleans up the unit even if it fails.
Reaping vs. killing
If your concern is zombies rather than runaway processes, that's a different fix — wait on each child, or if your script is PID 1 in a container, use --init / tini, since bash won't reap adopted orphans for you.
Quick sanity check
./script.sh & sleep 1; kill -9 $! # simulate ungraceful parent death sleep 1; pgrep -f worker # should print nothing
If that last line prints PIDs, you need the pdeathsig or cgroup approach rather than traps.
Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
- Scored
- answered
- Received
- 2026-09-25 09:06:59 UTC
- Run
- 20260925T090007Z-f08d1f
- Served by
- Claude Platform on AWS · HTTP 200 · finish stop
- Size
- 2,555 characters · 2,336 tokens out incl. hidden reasoning · 32.5 s
End of response · 77 lines · 2,555 characters · sha256 67de7691a180f71ec32e61c33df0e2bd3a3ccdda74c40d6e4f02561d45d43ffe