MODELDRIFT.WATCH

Question 13 · Sep 23 · Day 14

Claude on Question 13, Sep 23

How do I kill a zombie process in Linux that won't respond to SIGTERM?

The short answer: you can't kill a zombie

A zombie (state Z, sometimes shown as defunct) is already dead. It has exited, released its memory, closed its file descriptors — all that remains is an entry in the process table holding its exit status, waiting for its parent to call wait()/waitpid() to collect it.

Since there's no running code, signals have nowhere to go. SIGTERM, SIGKILL, kill -9 — all ignored, by design.

The fix is always to deal with the parent.

Find the zombies and their parents

ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'

Or a quick count:

ps -eo stat | grep -c '^Z'

Step 1: Nudge the parent with SIGCHLD

The parent is supposed to reap on SIGCHLD. If it has a handler but missed the notification, re-sending it can work:

kill -s SIGCHLD <PPID>

This is harmless and worth trying first. It often does nothing, though, because the usual cause is a parent that never reaps at all.

Step 2: Restart or kill the parent

When the parent dies, its zombie children are re-parented to PID 1 (init/systemd), which reaps them immediately.

# Graceful first
kill <PPID>

# Or for a service
systemctl restart <service>

If the parent is a service you can't restart casually, SIGKILL on the parent works too, but you'll lose any in-flight work.

Step 3: Figure out why the parent is stuck

If the parent isn't reaping, it's either buggy or blocked. Check:

cat /proc/<PPID>/status | grep -i state
cat /proc/<PPID>/wchan        # what kernel function it's sleeping in
sudo strace -p <PPID>         # is it doing anything?

A parent in state D (uninterruptible sleep) is typically stuck on I/O — a hung NFS mount, a failing disk, a dead network filesystem. That's the real problem; the zombies are just a symptom, and the parent won't respond to SIGKILL either until the I/O resolves.

Do you even need to act?

A zombie costs a process-table slot and a PID. A handful is cosmetic noise. It only becomes a real problem when they accumulate toward:

cat /proc/sys/kernel/pid_max     # usually 32768 or 4194304

Exhausting that means nothing on the system can fork — that's when you get "cannot allocate memory" or "fork: retry" errors.

Preventing it in your own code

C: either reap explicitly, or tell the kernel you don't care:

signal(SIGCHLD, SIG_IGN);   // kernel auto-reaps, no zombies ever

…or handle it properly:

void reaper(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}
signal(SIGCHLD, reaper);

The while loop matters — signals coalesce, so one SIGCHLD may represent several exits.

Python: always .wait() or .communicate() on subprocess.Popen objects. Fire-and-forget Popen calls are the classic zombie factory.

Shell: wait after backgrounding jobs.

Containers: this bites people constantly. If your app runs as PID 1 in Docker, it inherits orphans but usually has no reaping logic. Fix with:

docker run --init ...

or add tini/dumb-init as your entrypoint. In Kubernetes, set shareProcessNamespace: true or just use a proper init in the image.

sha256 5ccebb8d945e… · 33 tokens in · 1,896 out · 26.3 s · via Claude Platform on AWS · stopped on its own

Sep 23, the whole dayQuestion 13, every day

Exact recordShowHideThe model id, the run, the verbatim text with line numbers and its full sha256
Model
anthropic/claude-opus-5
pinned anthropic/claude-opus-5-20260723; alias resolved to anthropic/claude-opus-5-20260723 at 2026-09-23 09:00:22 UTC (matches the pin)
Date
2026-09-23 · run 20260923T090022Z-9b7b3a · scorer v1
Scored
answered
sha256
5ccebb8d945efa2edff8bc1e309e68556f1b00cca57c5b1c898cf9e94dd83672
Permalink
https://modeldrift.watch/r/2026-09-23/R1/anthropic/claude-opus-5/
Scored
answered
Received
2026-09-23 09:06:16 UTC
Run
20260923T090022Z-9b7b3a
Served by
Claude Platform on AWS · HTTP 200 · finish stop
Size
3,181 characters · 1,896 tokens out incl. hidden reasoning · 26.3 s
## The short answer: you can't kill a zombie
A zombie (state `Z`, sometimes shown as `defunct`) is **already dead**. It has exited, released its memory, closed its file descriptors — all that remains is an entry in the process table holding its exit status, waiting for its parent to call `wait()`/`waitpid()` to collect it.
Since there's no running code, signals have nowhere to go. `SIGTERM`, `SIGKILL`, `kill -9` — all ignored, by design.
**The fix is always to deal with the parent.**
## Find the zombies and their parents
```bash
ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'
```
Or a quick count:
```bash
ps -eo stat | grep -c '^Z'
```
## Step 1: Nudge the parent with SIGCHLD
The parent is supposed to reap on `SIGCHLD`. If it has a handler but missed the notification, re-sending it can work:
```bash
kill -s SIGCHLD <PPID>
```
This is harmless and worth trying first. It often does nothing, though, because the usual cause is a parent that never reaps at all.
## Step 2: Restart or kill the parent
When the parent dies, its zombie children are re-parented to PID 1 (`init`/`systemd`), which reaps them immediately.
```bash
# Graceful first
kill <PPID>
# Or for a service
systemctl restart <service>
```
If the parent is a service you can't restart casually, `SIGKILL` on the parent works too, but you'll lose any in-flight work.
## Step 3: Figure out *why* the parent is stuck
If the parent isn't reaping, it's either buggy or blocked. Check:
```bash
cat /proc/<PPID>/status | grep -i state
cat /proc/<PPID>/wchan # what kernel function it's sleeping in
sudo strace -p <PPID> # is it doing anything?
```
A parent in state `D` (uninterruptible sleep) is typically stuck on I/O — a hung NFS mount, a failing disk, a dead network filesystem. That's the real problem; the zombies are just a symptom, and the parent won't respond to `SIGKILL` either until the I/O resolves.
## Do you even need to act?
A zombie costs a process-table slot and a PID. A handful is cosmetic noise. It only becomes a real problem when they accumulate toward:
```bash
cat /proc/sys/kernel/pid_max # usually 32768 or 4194304
```
Exhausting that means nothing on the system can fork — that's when you get "cannot allocate memory" or "fork: retry" errors.
## Preventing it in your own code
**C:** either reap explicitly, or tell the kernel you don't care:
```c
signal(SIGCHLD, SIG_IGN); // kernel auto-reaps, no zombies ever
```
…or handle it properly:
```c
void reaper(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
signal(SIGCHLD, reaper);
```
The `while` loop matters — signals coalesce, so one `SIGCHLD` may represent several exits.
**Python:** always `.wait()` or `.communicate()` on `subprocess.Popen` objects. Fire-and-forget `Popen` calls are the classic zombie factory.
**Shell:** `wait` after backgrounding jobs.
**Containers:** this bites people constantly. If your app runs as PID 1 in Docker, it inherits orphans but usually has no reaping logic. Fix with:
```bash
docker run --init ...
```
or add `tini`/`dumb-init` as your entrypoint. In Kubernetes, set `shareProcessNamespace: true` or just use a proper init in the image.

End of response · 96 lines · 3,181 characters · sha256 5ccebb8d945efa2edff8bc1e309e68556f1b00cca57c5b1c898cf9e94dd83672