Task runner reports ENOSPC on subprocess output despite disk having free space
Bug
Claude Code's Bash tool reports ENOSPC when capturing any subprocess stdout, silently losing all command output. Commands with no output succeed; commands with any output fail.
Error message
Command output was lost: the temp filesystem at /private/tmp/claude-501/<project>/<session-uuid>/tasks is full (0MB free). The child process's stdout/stderr writes failed with ENOSPC.
Environment
- macOS Darwin 24.6.0
- Shell: zsh
CLAUDE_CODE_TMPDIR=/tmp/claude-501(already set — resolves to/private/tmp/claude-501via symlink on macOS)
Diagnostics
df -hon the tasks directory: 23 GB free on/dev/disk1s2(APFS, 90% used)- APFS container free: 24.6 GB
du -sh /private/tmp/claude-501/: 8 KB (essentially empty)- Task
.outputfiles are created at 0 bytes — write never completes - Direct shell writes to the same directory succeed:
echo "test" > .../tasks/test.txt→ 5 bytes, readable quota -v→ no quotaulimit -f→ unlimited- No extended attributes on the tasks directory
Key observation
The session UUID is per-conversation — the same UUID appears across multiple Claude Code relaunches within the same conversation. Once the UUID enters this broken state, all subsequent Bash tool invocations in that conversation lose their output. Starting a new conversation gets a new UUID and clears the problem.
Impact
All Bash tool command output is silently lost for the duration of the affected conversation. The tool appears to succeed (exit 0) but returns no output.
Hypothesis
The task runner's subprocess-to-file pipe write is returning ENOSPC at the OS level despite available disk space. Possible causes: (1) incorrect internal "available space" tracking that hits 0 before the real disk does, (2) a pipe buffer issue where the reader isn't draining concurrently with subprocess execution, or (3) a per-session-UUID state corruption.
Showing cached comments. Read the full discussion on GitHub ↗
11 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Same issue on macOS, Claude Code v2.1.158 (latest on npm). Additional data points:
sandboxkey in any settings.json), so this is not sandbox-overlay related — it happens with the plain task runner.CLAUDE_CODE_TMPDIRto a real disk does not help: pointed it at~/.claude/tmp(APFS, 178 GiB free, inode usage 0%) and relaunched — same0MB freeENOSPC under the new path; thetasksdir is empty (ls→ total 0).claude --continuedoes not help (it reuses the same session UUID).echopass), then the next external process (rg,ls,git) hitsENOSPC — and this recurs in every new session. So the corrupted "available space" state isn't merely stuck to one UUID; new UUIDs degrade to the same failure quickly.
echosucceeds; fork/exec processes fail; the captured.outputis created at 0 bytes.Version: 2.1.158 / macOS. (Possibly related to the empty-output reports in #63797, though there the failure is silent empty output on Linux rather than an explicit ENOSPC.)
Reproduced independently — adding a second data point plus a concrete impact case and a mitigation that helps with one failure mode.
Environment
CLAUDE_CODE_TMPDIRin useSame false-ENOSPC signature
Identical error string:
Meanwhile the disk was nowhere near full:
diskutil info(the truthful number, incl. purgeable): 27.4 GB freeclaude-*temp roots combined: ~10 MB (largest single root 5.7 MB)So this is not real disk exhaustion and not inode exhaustion. Confirming OP's read: it's the task-runner's subprocess-output capture failing at the OS level despite free space.
Confirming intra-session, and the per-conversation-UUID trap
I can corroborate OP's key observation: the broken state is keyed to the per-conversation session UUID and persists across relaunches within the same conversation. The failing
tasksdir lived inside a single session's own private root — no other instance could touch it — so for that occurrence it was an intra-session self-race (the harness's own temp cleanup racing its own output write), not a cross-session collision.(For completeness, the cross-session variant is also real and tracked separately, e.g. #49512 / #25292, where concurrent sessions sharing
/tmp/claude-$UIDrace-delete each other'stasks/. Both classes exist; this issue is the single-session one.)Why this is worse than "lost output" — it silently dropped file edits that shipped to prod
The most damaging part isn't the missing stdout — it's that the scrambled/empty tool output masked failed
Editcalls. In one batch, 4 edits failed (badold_string/ unread-file) but the failures were hidden by the corrupted output, the tool chain reported success, and the change was committed and deployed to production with those 4 edits missing. It was only caught afterward by checking the deployed artifact (a prod sitemap that had 0 of the expected URLs), then fixed in a follow-up commit. So the blast radius of this bug includes silent partial-edits reaching prod, not just cosmetic output loss — which lines up with the "tool appears to succeed (exit 0) but returns no output" impact OP described.CLAUDE_CODE_TMPDIRto a roomy disk does not fix itConfirming the May-31 comment: pointing
CLAUDE_CODE_TMPDIRat a filesystem with plenty of free space does not resolve it, because a relaunch within the same conversation reuses the same poisoned session-UUID dir.Partial mitigation that does help (for the relaunch-persistence trap)
A shell wrapper that gives each launch a distinct per-PID temp root outside
/tmpstops a relaunch from inheriting a poisonedtasksdir, and moves the scratch dir out of reach of the macOS/tmpreaper:This does not stop the race from occurring mid-conversation (that's upstream), but it prevents the "stays broken across relaunch until you start a brand-new conversation" failure mode.
Suggested upstream fix direction
The underlying defect looks like the output-capture
open()/write path treating a transient failure (or a cleanup-deleted dir) as ENOSPC and then surfacing it as "0MB free." Two things would sharply reduce blast radius even before the race is fixed:statfsactually shows ~0 — report the real errno (often ENOENT from a deleted dir).Edit/Bashfailures hard-fail loudly rather than being swallowable by corrupted capture, so a partial edit can't silently pass through to a commit.Happy to provide more diagnostics if useful.
One additional data point not yet in the thread: within-session inconsistency. In the same session, git status and cat succeeded while git branch failed with ENOSPC.
Noticed Claude trying to set
CLAUDE_CODE_TMPDIRin the middle of the conversation, even that doesn’t seem to fix the issue.Claude’s diagnosis:
macOS repro (Darwin 23.6.0 / macOS Sonoma)
Hitting this in a long interactive session on macOS — not a Cowork VM.
Setup:
Bashtask calls over ~45 minutes/Users/<user>/.claude/tmp/claude-501/-Users-<user>/<session-id>/tasks/Repro:
Bashcalls (run_in_background: true)``
``Command output was lost: the temp filesystem at .../tasks is full (0MB free).
The child process's stdout/stderr writes failed with ENOSPC.
df -hon the tasks dir shows 14GB free on the underlying/dev/disk1s1filesystem — this is a false ENOSPC on a normal macOS APFS volume, not a tmpfs at all.outputfiles from the tasks dir (total ~8KB!) unblocks the next command temporarily, but it recursKey difference from other repros: This is not a tmpfs or disk-full scenario. The tasks dir sits on a normal APFS volume with gigabytes free. The ENOSPC is internal to how Claude Code manages the tasks subprocess output buffer — the 4 accumulated
.outputfiles from completed background tasks total only 8KB yet trigger the error.Workaround:
rm ~/.claude/tmp/claude-501/-Users-<user>/<session-id>/tasks/*.outputclears it temporarily.Can confirm same issue observed, on a very similar setup and virtually identical session meta:
Corroborating repro — deterministic, with control tests isolating the exact trigger (matches the
statfs bsize=0analysis in #65166)Environment: Claude Code 2.1.156, desktop app local-agent mode, macOS Darwin 25.5.0, Intel x86_64, APFS with 105 GiB genuinely free (
dfanddiskutilcontainer-free agree, so no purgeable-space mirage).The trigger is precisely: empty captured output AND nonzero exit. Each of these is 100% reproducible, not intermittent:
| Command | Output | Exit | Result |
|---|---|---|---|
|
false| none | 1 | ❌ "temp filesystem … is full (0MB free) … ENOSPC" every time ||
true| none | 0 | ✅ clean ||
sh -c 'echo hi; exit 1'| stdout | 1 | ✅ output captured fine ||
sh -c 'echo err >&2; exit 3'| stderr | 3 | ✅ output captured fine ||
dd if=/dev/zero of=<tasks dir>/.t bs=1m count=5| — | 0 | ✅ 5 MB written into the "full" dir at ~1.9 GB/s |This exactly fits #65166's decompiled handler (
stdout === "" && exitCode !== 0→explainTmpFailure→ Bunfs.statfsreturningbsize = 0on darwin-x64 → computed free = 0 MB → always reports full). Nothing is actually lost — in every reproduced case the "lost" output was genuinely empty.Impact beyond cosmetics: the message reads as authoritative, so agents (and users) burn real time on phantom disk cleanup. On this machine it drove three separate "disk full" investigation sessions across a week — inventing purgeable-churn theories, deleting VM images, restarting Docker — before controlled tests showed every "ENOSPC" was just a silently-failing command (
grep/lsofwith no match). Suggested fix until the Bun statfs fix (oven-sh/bun#31139) ships in a release: treatbsize == 0as "unknown", not "full", and/or verify with a real test write before claiming ENOSPC.---
Environment:
What happens:
grep with no matches silently fails with a false ENOSPC error every time:
Command output was lost: the temp filesystem at [...]/tasks is full (0MB free).
The child process's stdout/stderr writes failed with ENOSPC.
Exact reproduction:
grep -n "nonexistent_pattern" anyfile.md # → false ENOSPC, exit 1
echo "hello" # → works fine
The pattern is precisely: external binary + no stdout + exit code 1. Shell built-ins are unaffected.
Impact: grep is effectively broken for the common "check if pattern exists, expect no match" case. This affects any Claude Code session doing file search, log inspection, or validation checks. Our session management scripts that use grep are also at risk if they ever hit a no-match condition.
Workaround: Replace grep with a pure-bash while read loop. This is not a reasonable long-term solution for real workflows.
Changing CLAUDE_CODE_TMPDIR does not help — the issue is in the subprocess output capture mechanism, not the filesystem.
---
Possible workaround would be to force agent to act differently by hooks. This fixed the problem for me, at least it doesn't interrupt the flow:
This will instruct agent to retry with success. Works for me.
The more clear approach may be to use
updatedToolOutputfeature. Implement retry in hook itself -> hijack output. But this needs more debugging. Agent self healing is easier for now and works.Thanks!