MODELDRIFT.WATCH

Question 14 · Sep 14 · Day 5

Gemini on Question 14, Sep 14

I'm writing a bash script to purge orphaned children after the parent dies. What's the idiomatic way?

The most idiomatic, pure-bash way to clean up child processes is to use the trap builtin combined with jobs -p or process group signaling, hooked to the EXIT pseudo-signal.

Here are the two standard approaches, depending on whether your child processes spawn their own children.

1. The Standard Idiom (Direct Children Only)

If your script spawns background processes (&) and you only need to kill those direct children, use jobs -p:

#!/bin/bash

# 1. Set the trap to kill all background jobs spawned by this shell
trap 'kill $(jobs -p) 2>/dev/null' EXIT

# 2. Start some background tasks
sleep 100 &
sleep 200 &

echo "Running... Press Ctrl+C or let the script exit to see cleanup."
sleep 5

Why this works:

  • •EXIT: This pseudo-signal is triggered when the script exits naturally, errors out (if set -e is used), or receives a standard termination signal (SIGINT, SIGTERM).
  • •jobs -p: Returns the Process IDs (PIDs) of all background jobs currently tracked by the shell.
  • •2>/dev/null: Suppresses error messages just in case some of the children have already finished and exited on their own.

2. The "Process Group" Idiom (Children and Grandchildren)

If your background tasks spawn their own subprocesses, jobs -p isn't enough (the grandchildren will be orphaned). You need to kill the entire process group.

#!/bin/bash

# Send SIGTERM to the script's entire process group
trap 'trap - EXIT; kill 0' EXIT

# Start a child that spawns a grandchild
( sleep 10 ; sleep 20 ) &

echo "Running..."
sleep 5

Why this works:

  • •kill 0: Sends a SIGTERM to all processes in the current process group.
  • •trap - EXIT: This is crucial. Because kill 0 sends a signal to the whole group (which includes the parent script itself), it can cause an infinite loop where the script catches its own exit, tries to exit again, and triggers the trap again. Unsetting the trap first prevents this.

⚠️ The Fatal Flaw of Bash Traps (SIGKILL)

Both methods above rely on the shell catching a signal and running the trap. They will fail if the parent script is killed with SIGKILL (kill -9). SIGKILL cannot be intercepted by bash.

If you need bulletproof protection against kill -9 or Out-Of-Memory (OOM) kills, Bash alone cannot help you. You must use operating system features:

Linux-specific bulletproof alternative (systemd):
If you are on Linux, you can run your script inside a temporary systemd scope. Systemd uses cgroups, meaning when the scope ends or is killed, the kernel ruthlessly wipes out every child and grandchild inside it automatically.

# Run your script this way instead of directly:
systemd-run --user --scope ./your_script.sh

Linux-specific bulletproof alternative (prctl):
If you are writing wrappers in C, Python, or Go, you can use the prctl(PR_SET_PDEATHSIG, SIGTERM) system call. This tells the Linux kernel to automatically send a signal to the child when the parent thread dies, regardless of how the parent died. (Bash doesn't have access to this without external C-bindings/binaries like pdeathsig).

sha256 64e1758a61f5… · 24 tokens in · 2,287 out · 21.2 s · via Google · stopped on its own

Sep 14, the whole dayQuestion 14, every day

Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
Model
google/gemini-3.1-pro-preview
pinned google/gemini-3.1-pro-preview-20260219; alias resolved to google/gemini-3.1-pro-preview-20260219 at 2026-09-14 09:00:21 UTC (matches the pin)
Date
2026-09-14 · run 20260914T090022Z-e98262 · scorer v1
Scored
answered
sha256
64e1758a61f540dcef19322c7e60f48a746a6179b805572af7f20550d37a803f
Permalink
https://modeldrift.watch/r/2026-09-14/R5/google/gemini-3.1-pro-preview/
Scored
answered
Received
2026-09-14 09:07:57 UTC
Run
20260914T090022Z-e98262
Served by
Google · HTTP 200 · finish stop
Size
3,149 characters · 2,287 tokens out incl. hidden reasoning · 21.2 s
The most idiomatic, pure-bash way to clean up child processes is to use the `trap` builtin combined with `jobs -p` or process group signaling, hooked to the `EXIT` pseudo-signal.
Here are the two standard approaches, depending on whether your child processes spawn *their own* children.
### 1. The Standard Idiom (Direct Children Only)
If your script spawns background processes (`&`) and you only need to kill those direct children, use `jobs -p`:
```bash
#!/bin/bash
# 1. Set the trap to kill all background jobs spawned by this shell
trap 'kill $(jobs -p) 2>/dev/null' EXIT
# 2. Start some background tasks
sleep 100 &
sleep 200 &
echo "Running... Press Ctrl+C or let the script exit to see cleanup."
sleep 5
```
**Why this works:**
* `EXIT`: This pseudo-signal is triggered when the script exits naturally, errors out (if `set -e` is used), or receives a standard termination signal (`SIGINT`, `SIGTERM`).
* `jobs -p`: Returns the Process IDs (PIDs) of all background jobs currently tracked by the shell.
* `2>/dev/null`: Suppresses error messages just in case some of the children have already finished and exited on their own.
### 2. The "Process Group" Idiom (Children and Grandchildren)
If your background tasks spawn their own subprocesses, `jobs -p` isn't enough (the grandchildren will be orphaned). You need to kill the entire **process group**.
```bash
#!/bin/bash
# Send SIGTERM to the script's entire process group
trap 'trap - EXIT; kill 0' EXIT
# Start a child that spawns a grandchild
( sleep 10 ; sleep 20 ) &
echo "Running..."
sleep 5
```
**Why this works:**
* `kill 0`: Sends a `SIGTERM` to all processes in the current process group.
* `trap - EXIT`: This is **crucial**. Because `kill 0` sends a signal to the whole group (which includes the parent script itself), it can cause an infinite loop where the script catches its own exit, tries to exit again, and triggers the trap again. Unsetting the trap first prevents this.
---
### ⚠️ The Fatal Flaw of Bash Traps (SIGKILL)
Both methods above rely on the shell catching a signal and running the `trap`. **They will fail if the parent script is killed with `SIGKILL` (`kill -9`).** `SIGKILL` cannot be intercepted by bash.
If you need bulletproof protection against `kill -9` or Out-Of-Memory (OOM) kills, Bash alone cannot help you. You must use operating system features:
**Linux-specific bulletproof alternative (`systemd`):**
If you are on Linux, you can run your script inside a temporary `systemd` scope. Systemd uses cgroups, meaning when the scope ends or is killed, the kernel ruthlessly wipes out every child and grandchild inside it automatically.
```bash
# Run your script this way instead of directly:
systemd-run --user --scope ./your_script.sh
```
**Linux-specific bulletproof alternative (`prctl`):**
If you are writing wrappers in C, Python, or Go, you can use the `prctl(PR_SET_PDEATHSIG, SIGTERM)` system call. This tells the Linux kernel to automatically send a signal to the child when the parent thread dies, regardless of *how* the parent died. (Bash doesn't have access to this without external C-bindings/binaries like `pdeathsig`).

End of response · 63 lines · 3,149 characters · sha256 64e1758a61f540dcef19322c7e60f48a746a6179b805572af7f20550d37a803f