Session hangs when bash subprocess becomes zombie (defunct)

Resolved 💬 6 comments Opened Jan 28, 2026 by Gotrek77 Closed Mar 10, 2026

Description

Claude Code sessions can become permanently stuck when a bash subprocess terminates abnormally and becomes a zombie process. The parent Claude process fails to reap the zombie child, causing the entire session to hang indefinitely.

Steps to Reproduce

  1. Start a Claude Code session with --allow-dangerously-skip-permissions
  2. Run long-running bash commands (e.g., filesystem operations, build scripts, flashing operations)
  3. If a subprocess terminates abnormally (e.g., due to signals, I/O errors), it may become a zombie
  4. The Claude session becomes unresponsive

Observed Behavior

$ ps aux | grep claude
patane   1357509 78.5  6.6 ... Rl+ ... claude --allow-dangerously-skip-permissions

$ ps aux | grep defunct
patane   1371523  0.0  0.0  0  0 ?  Zs  07:02  0:00 [bash] <defunct>

$ ps -eo pid,ppid,stat | grep 1371523
1371523 1357509 Z

The zombie bash process (PID 1371523) is a child of the Claude process (PID 1357509). The Claude session is stuck and unresponsive, requiring kill -9 to terminate.

Expected Behavior

Claude should properly handle SIGCHLD signals and reap zombie children, or implement a timeout mechanism to detect and recover from stuck states.

Environment

  • OS: Ubuntu 24.04 (Linux 6.17.0-8-generic)
  • Claude Code version: Latest (via npm)
  • Node.js: v20+
  • Shell: bash

Workaround

We've implemented a watchdog service that monitors for stuck Claude sessions and automatically kills them:

1. Watchdog Script (~/.local/bin/claude-watchdog)

#!/bin/bash
# Claude Watchdog - Monitors and cleans up stuck Claude sessions

LOG_FILE="${HOME}/.claude/watchdog.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

check_zombies() {
    for claude_pid in $(pgrep -f "claude.*--allow"); do
        zombie_count=$(ps --ppid "$claude_pid" -o stat= 2>/dev/null | grep -c "Z")
        if [ "$zombie_count" -gt 0 ]; then
            log "WARNING: Claude PID $claude_pid has $zombie_count zombie children"
            cpu_usage=$(ps -p "$claude_pid" -o %cpu= 2>/dev/null | tr -d ' ')
            if [ -n "$cpu_usage" ] && (( $(echo "$cpu_usage < 1" | bc -l) )); then
                log "Claude PID $claude_pid appears stuck (CPU: ${cpu_usage}%)"
                return 1
            fi
        fi
    done
    return 0
}

kill_stuck_sessions() {
    for claude_pid in $(pgrep -f "claude.*--allow"); do
        zombie_count=$(ps --ppid "$claude_pid" -o stat= 2>/dev/null | grep -c "Z")
        cpu_usage=$(ps -p "$claude_pid" -o %cpu= 2>/dev/null | tr -d ' ')
        if [ "$zombie_count" -gt 0 ] && [ -n "$cpu_usage" ] && (( $(echo "$cpu_usage < 1" | bc -l) )); then
            log "KILLING stuck Claude session PID $claude_pid"
            kill -9 "$claude_pid" 2>/dev/null
        fi
    done
}

# Daemon mode
log "Starting Claude watchdog daemon"
while true; do
    if ! check_zombies; then
        kill_stuck_sessions
    fi
    sleep 60
done

2. Systemd User Service (~/.config/systemd/user/claude-watchdog.service)

[Unit]
Description=Claude Code Watchdog - Monitor and clean stuck sessions
After=default.target

[Service]
Type=simple
ExecStart=%h/.local/bin/claude-watchdog --daemon
Restart=on-failure
RestartSec=10

[Install]
WantedBy=default.target

Enable with:

systemctl --user daemon-reload
systemctl --user enable --now claude-watchdog.service

Suggested Fix

  1. Implement proper SIGCHLD signal handling in the Node.js process to reap zombie children
  2. Add a subprocess timeout mechanism for bash commands
  3. Consider using child_process options like detached: false and proper cleanup on exit
  4. Implement a health check that detects unresponsive states and auto-recovers

Additional Context

This issue occurs more frequently during:

  • Long-running build/compile operations
  • Filesystem operations on mounted drives
  • Operations that spawn multiple subprocesses
  • When using --allow-dangerously-skip-permissions mode

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗