Backgrounded sudo processes escape Bash tool timeout, causing unbounded disk growth (300GB+)
Backgrounded sudo processes escape Bash tool timeout/cleanup, causing unbounded disk usage
Summary
When the Bash tool runs a command that spawns a sudo-elevated background process, the process survives the tool's timeout and continues writing to the tool-results file indefinitely. In my case, a single fs_usage trace ran for 2.5 hours undetected and produced a 371 GiB tool-results file.
Reproduction
- In a Claude Code session, ask something like "find resource-hogging processes system-wide"
- Claude generates a command like:
``bash`
sudo fs_usage -w -f filesys 2>/dev/null & PID=$!; sleep 3; kill $PID 2>/dev/null; wait $PID 2>/dev/null | sort | uniq -c | sort -rn | head -20
timeout: 15000` (15s)
with a
- The
PID=$!assignment fails in zsh (command not found: PID=), so thekillis a no-op - The 15s timeout fires, Claude's bash process exits, and ~30KB of stdout is captured successfully (
interrupted: false) sudo fs_usage(owned by root, PID 1 parent) keeps running indefinitely, still writing to:
````
~/.claude/projects/<project>/<session>/tool-results/<id>.txt
- Claude analyzes the partial output and continues the conversation, unaware the process is still alive
- The tool-results file grows without bound — in my case to 371 GiB before I manually discovered and killed it
Root Cause
Three compounding issues:
1. No cleanup of orphaned child processes after timeout
When the Bash tool's timeout kills the shell, child processes spawned via sudo reparent to PID 1 (launchd/init). The tool has no mechanism to track or kill these orphans. Regular (non-sudo) background processes may also escape if they detach from the process group.
2. Shell syntax incompatibility (zsh)
The generated command uses & PID=$!; ... which doesn't work in zsh — the PID=$! is parsed as a separate command after the & and fails. This means the intended self-cleanup (kill $PID) never fires. Claude doesn't notice the error in stderr/stdout because it's mixed into the trace output.
3. No safeguard on tool-results file size
There is no monitoring, cap, or circuit breaker on tool-results files. A file can grow to hundreds of GiB with nothing flagging it. Even a simple periodic size check or a max file size limit (e.g., 100 MB) would prevent this class of failure.
Impact
- Disk space: 371 GiB consumed silently by a single file in
~/.claude/ - Backup bloat: My incremental backup (rustic/restic) jumped from ~300 GiB to ~570 GiB mid-run because of this file
- CPU:
fs_usagewas running at 100% CPU for 2.5 hours - Silent failure: No warning, no error, no indication anything was wrong. The session continued normally.
Suggested Fixes
- Process group cleanup: Run Bash tool commands in their own process group (
setsidorpgid). On timeout, kill the entire process group, not just the shell. Forsudochildren, considersudo killof known child PIDs.
- Tool-results file size limit: Cap tool-results files at a reasonable size (e.g., 50–100 MB). Truncate and warn if exceeded.
- Orphan process detection: After a Bash tool invocation completes, check if any child processes are still running and warn/kill them.
- Shell compatibility: Ensure generated commands work in both bash and zsh, or detect the user's shell and adapt. The
& PID=$!pattern is a known zsh pitfall.
Environment
- Claude Code version: 2.1.63
- Shell: zsh
- Platform: macOS (Darwin 25.2.0, Apple Silicon)
- Model: claude-opus-4-6
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗