Claude on Question 14, Sep 16
I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?The short answer
Trap and kill your own process group. That's the idiom:
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
trap - EXIT INT TERM # disarm so we don't recurse
kill -- -$$ 2>/dev/null || true
}
trap cleanup EXIT INT TERM
long_thing &
another_thing &
waitThe -$$ means "the process group whose ID is our PID" — that catches children and grandchildren in one shot.
Caveat: this only works if your script is actually the process group leader. If it was invoked from another script, it inherits that group and you'll nuke your caller too. Either run it under setsid, or enable job control so each background job gets its own group:
set -m
If you want to be surgical
Track PIDs yourself rather than carpet-bombing:
declare -a children=()
spawn() { "$@" & children+=("$!"); }
cleanup() {
trap - EXIT
for pid in "${children[@]}"; do
kill "$pid" 2>/dev/null || true
done
wait 2>/dev/null || true
}
trap cleanup EXIT
spawn sleep 300
spawn tail -f /var/log/something
waitThis misses grandchildren, though. To fix that, give each child its own process group and kill the group:
spawn() { setsid "$@" & children+=("$!"); } # PID == PGID for setsid'd procs
# then: kill -- "-$pid"When the parent gets SIGKILL'd
Traps don't run on SIGKILL, and orphans get reparented to PID 1 (or the nearest subreaper) — they just keep running. Two options:
Linux, util-linux ≥ 2.33 — let the kernel do it:
setpriv --pdeathsig TERM my_child &
This sets PR_SET_PDEATHSIG, so the kernel signals the child the instant its parent dies, no matter how.
Portable pipe watchdog — the child blocks on a pipe the parent holds open. Parent dies → EOF → child exits:
# parent
coproc WATCHDOG { child_script.sh; }
# child_script.sh reads fd 0; when parent's write end closes, read returnsOr explicitly with a FIFO:
fifo=$(mktemp -u); mkfifo "$fifo"
exec {keepalive}>"$fifo"
child_with_watchdog "$fifo" &
rm "$fifo" # unlink; the open fds keep it alive…where the child runs read <"$fifo"; kill $workers in a background thread. Clunky in pure bash but dependency-free.
The nuclear option
If you're on systemd and want guaranteed cleanup, put the whole tree in a transient scope and let the cgroup handle it:
systemd-run --user --scope --collect ./my_script.sh
Kill the scope, everything inside dies. No orphan bookkeeping at all. This is what I'd reach for if the process tree is deep or third-party code is doing the forking.
Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
- Scored
- answered
- Received
- 2026-09-16 09:06:48 UTC
- Run
- 20260916T090004Z-fdcf7b
- Served by
- Claude Platform on AWS · HTTP 200 · finish stop
- Size
- 2,590 characters · 2,280 tokens out incl. hidden reasoning · 32.4 s
End of response · 97 lines · 2,590 characters · sha256 6aa12c2f0300094c9a7b773063ba76e9b7d9cafebd7791c4bdbcdde0d50ef8fc