Task .output files in tmpdir grow unbounded and are never cleaned up

Resolved 💬 3 comments Opened Mar 15, 2026 by esperie Closed Mar 15, 2026

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 falsedeleteOutputFile() 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

  1. Start a Claude Code session
  2. Run a bash command with run_in_background: true that produces continuous output (e.g., a build with verbose logging, or something like yes | head -c 1G)
  3. Let the command complete
  4. Check the .output file size in /tmp/claude-<uid>/*/tasks/
  5. Start a new session — the old .output files remain

Environment

  • Claude Code v2.1.76
  • macOS Darwin 25.3.0 (arm64)
  • 3 days uptime, accumulated ~2.5 GB in .output files across sessions (after manually deleting a 2.3 GB file; previously had 2x ~1 TB files)

Suggested fix

  1. Add a max file size to the Y91 disk writer (e.g., 50–100 MB). When exceeded, truncate from the head or stop writing, keeping only the tail.
  2. Delete .output files 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 call deleteOutputFile().
  3. Add session-end cleanup — delete all .output files in the current session's tasks directory during graceful shutdown.
  4. Add startup cleanup — on session start, scan for and remove .output files from old/stale session directories.

View original on GitHub ↗

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