Task .output files in tmpdir grow unbounded and are never cleaned up
Description
Task .output files under the Claude Code temp directory (/tmp/claude-<uid>/<session-id>/tasks/*.output) grow without any size limit and are never cleaned up, leading to disk exhaustion. I've observed individual files reaching 2.3 GB, and on a previous occasion two files grew to ~1 TB each before I noticed and manually deleted them.
Root cause (source analysis of v2.1.76)
After analyzing the bundled cli.js from the npm package, three issues are apparent:
1. No size cap on the disk writer (Y91 class)
When a TaskOutput (kw class) exceeds its 8 MB in-memory buffer threshold, it "spills to disk" via the Y91 file writer. Y91.append() opens the file with O_WRONLY | O_APPEND | O_CREAT and appends indefinitely — there is no maximum file size check. For background tasks (run_in_background: true or subagents), stdoutToFile is set to true, so output goes directly to disk from the start, also with no cap.
Any command that produces sustained output (builds, test suites, log tailing, verbose processes) will grow the .output file until the process ends or the disk fills.
2. .output files are never deleted for background/agent tasks
The only call to deleteOutputFile() is in the command completion handler (j38.#N), gated by:
if (this.taskOutput.stdoutToFile && !this.#q) // #q = backgroundTaskId
if (this.taskOutput.outputFileRedundant)
this.taskOutput.deleteOutputFile();
For background tasks, #q (the background task ID) is always set, so the condition !this.#q is always false — deleteOutputFile() is never reached.
When a background task completes, result.then() calls flush() then cleanup(). But cleanup() calls taskOutput.clear(), which only clears in-memory state and cancels the writer queue. It does not delete the .output file from disk:
clear() {
this.#A = ""; // clear stdout buffer
this.#q = ""; // clear stderr buffer
this.#z.clear(); // clear recent lines ring buffer
this.#$ = null; // clear polling callback
this.#K?.cancel(); // cancel writer queue (but file remains)
kw.stopPolling(this.taskId);
kw.#O.delete(this.taskId);
// NOTE: no deleteOutputFile() call here
}
3. No session-end or startup cleanup of orphaned .output files
The graceful shutdown handler (Vq) flushes telemetry, runs session-end hooks, and writes profiling data — but does not clean up the tasks directory. There is also no startup cleanup that removes .output files from previous sessions.
The task directory structure (/tmp/claude-<uid>/<session-id>/tasks/) is per-session, but old session directories accumulate indefinitely (until OS reboot clears /tmp). On macOS, users who rarely reboot can accumulate many GB of orphaned .output files.
Reproduction
- Start a Claude Code session
- Run a bash command with
run_in_background: truethat produces continuous output (e.g., a build with verbose logging, or something likeyes | head -c 1G) - Let the command complete
- Check the
.outputfile size in/tmp/claude-<uid>/*/tasks/ - Start a new session — the old
.outputfiles remain
Environment
- Claude Code v2.1.76
- macOS Darwin 25.3.0 (arm64)
- 3 days uptime, accumulated ~2.5 GB in
.outputfiles across sessions (after manually deleting a 2.3 GB file; previously had 2x ~1 TB files)
Suggested fix
- Add a max file size to the
Y91disk writer (e.g., 50–100 MB). When exceeded, truncate from the head or stop writing, keeping only the tail. - Delete
.outputfiles after consumption — when the task result has been read/returned to the model and the task is complete, delete the file. At minimum,clear()should calldeleteOutputFile(). - Add session-end cleanup — delete all
.outputfiles in the current session's tasks directory during graceful shutdown. - Add startup cleanup — on session start, scan for and remove
.outputfiles from old/stale session directories.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗