Temp-filesystem preflight false-positives ENOSPC on filesystems with >17.6 TB free (statfs 32-bit truncation)

Status Fixed / completed
Reported on v2.1.153
Maintainer reply None cached
Activity 4 comments · opened May 30, 2026 · closed Jun 1, 2026

Bug report: temp-filesystem preflight false-positives "ENOSPC" on large (>17.6 TB) filesystems

Product: Claude Code CLI
Affected versions: 2.1.153 → 2.1.158 (latest at time of writing). Not present in ≤ 2.1.152.
Severity: Tool execution aborts ("Command output was lost") on filesystems that are nowhere near full.

Summary

Since 2.1.153, Claude Code runs a free-space preflight before capturing a
child process's stdout/stderr to its temp filesystem. On a filesystem with
more than ~17.6 TB of free space, the check computes a negative free-MB
value (due to a 32-bit truncation of f_bavail in the JS runtime's fs.statfs
binding) and aborts with:

Command output was lost: the temp filesystem at <dir> is full (-4469490MB free).
The child process's stdout/stderr writes failed with ENOSPC.
Free up space or set CLAUDE_CODE_TMPDIR to a directory on a filesystem with room.

The filesystem is not full — the write would have succeeded. Versions ≤
2.1.152 (which had no preflight) ran on the exact same machine for weeks with
no issue.

Root cause

The preflight (decompiled from 2.1.158) is:

const q = await statfs(dir);
const K = Math.floor(q.bavail * q.bsize / 1048576); // free MB
if (K < 10) return `…is full (${K}MB free)… ENOSPC…`;
if (q.files > 0 && q.ffree < 1000) return `…out of inodes…`;

q.bavail comes from the runtime's fs.statfs, which truncates the kernel's
64-bit f_bavail to a signed 32-bit int. Measured on the affected host
(a containerd overlay rootfs, 112 TB / 45.8 TB free):

| field | kernel (os.statvfs, correct) | runtime (fs.statfs, what CC uses) |
|---|---|---|
| f_bavail | 11,740,712,350 | −1,144,189,538 |
| bavail × bsize | 48,089,957,785,600 (≈ 45.8 TB) | −4,686,600,347,648 |
| → free MB (K) | 45,862,157 | −4,469,491 |

The truncation is exact:

11,740,712,350 mod 2^32 = 3,150,777,758
3,150,777,758 > 2^31  →  as int32 = −1,144,189,538   ✓

So any filesystem with > 2^32 free 4 KB-blocks (≈ 17.6 TB free) trips
K < 10 and aborts. Large overlay/NFS/scratch filesystems are common on
build boxes, GPU nodes, and CI runners.

Reproduction

On a host with a >17.6 TB-free filesystem (or any fs whose fs.statfs().bavail
wraps negative — verify with the table below):

# kernel says plenty free:
python3 -c "import os;s=os.statvfs('/tmp');print(s.f_bavail*s.f_bsize//1048576,'MB')"
# runtime truncates to negative:
node -e "const s=require('fs').statfsSync('/tmp');console.log(Math.floor(s.bavail*s.bsize/1048576),'MB')"
# (bun reproduces it identically)

Then run any Bash tool that produces stdout — Claude Code aborts with the
false ENOSPC.

Suggested fixes (either is sufficient)

  1. Guard the preflight against bogus/negative values — treat a negative

(or otherwise nonsensical) free figure as "unknown, proceed" rather than
"full". Minimal:
``js
const K = Math.floor(q.bavail * q.bsize / 1048576);
if (K >= 0 && K < 10) return
…is full…; // skip the guard when K < 0
`
The actual
write()` already surfaces a real ENOSPC if the disk is truly
full, so a negative preflight reading should never block.

  1. Fix the underlying fs.statfs truncation in the bundled runtime so

f_bavail/f_blocks/f_bfree are read as 64-bit (this is a
Node/Bun-level binding issue; report there too).

Workaround (current)

Set CLAUDE_CODE_TMPDIR to a directory on a filesystem with < ~17.6 TB free
(any tmpfs such as /dev/shm qualifies), so the block count stays within
32 bits and the preflight reads a sane value.

View original on GitHub ↗

4 Comments

AmirL · 3 months ago

**Same preflight, second broken statfs path: Bun on macOS reports bsize = 0, so the check fails on any disk size (not just >17.6 TB)**

On macOS the installer build of Claude Code is a Bun single-file executable, and Bun's fs.statfsSync returns bsize = 0 for every path on APFS. That makes the preflight compute free MB = bavail × bsize ÷ 1048576 = N × 0 = 0, so it aborts with is full (0MB free) regardless of how much space is actually free. This is independent of the 32-bit truncation case in the original report — it triggers on normal-sized disks too.

Repro on macOS 15 (Darwin 24.6), APFS, ~196 GB free:

$ python3 -c "import os;s=os.statvfs('/private/tmp');print(s.f_bavail*s.f_frsize//1048576,'MB')"
200254 MB

$ node -e "const s=require('fs').statfsSync('/private/tmp');console.log(s.bsize, Math.floor(s.bavail*s.bsize/1048576),'MB')"
4096 200254 MB        # Node: correct

$ bun -e "const s=require('fs').statfsSync('/private/tmp');console.log(s.bsize, Math.floor(s.bavail*s.bsize/1048576),'MB')"
0 0 MB                # Bun: bsize=0 -> 0 MB, false positive

bsize is 0 for every path tried (/, $HOME, /private/tmp, project dir). Still reproduces on Bun 1.3.14 (current latest) — the version embedded in the Claude Code 2.1.159 native binary — so upgrading Bun does not help, and the bundled runtime can't be swapped.

Note this is not fixed by the proposed if (K >= 0 && K < 10) guard, because here K === 0, not negative. A robust guard needs to treat bsize === 0 (or any non-positive bsize) as "unknown — proceed and let the real write() surface a genuine ENOSPC", in addition to the negative-value case. Properly reading f_bsize/f_bavail as 64-bit in the Bun binding would fix it at the source.

Also worth noting for other macOS users hitting this: setting CLAUDE_CODE_TMPDIR does not help, since Bun returns bsize=0 for all paths.

maneeshjupiter · 2 months ago

Still present in 2.1.163 — Bun bsize=0 variant is unfixed

The {bigint: true} + negative-value guard in 2.1.163 fixes the original >17.6 TB truncation, but @AmirL's bsize=0 variant is still live. Confirmed and reproduced on macOS (Darwin 25.5.0, APFS, 68 GiB free).

Root cause chain:

  1. Claude Code 2.1.163 embeds Bun 1.3.14 (released May 13, 2026)
  2. Bun's statfs struct alignment bug (oven-sh/bun#31133) returns bsize=0 on macOS x86_64 APFS
  3. The fix (oven-sh/bun#31139) merged May 21 — 8 days after 1.3.14 shipped. No Bun release includes it yet.
  4. The preflight computes bavail * 0n / (1024n * 1024n) = 0n, and 0n < 10n triggers the "0MB free" error
  5. The existing q < 0n guard doesn't catch this since 0n is not negative

Trigger condition: any command that exits non-zero with empty stdout (e.g. grep with no matches, ls | grep of a missing pattern). The diagnostic runs when stdoutToFile && stdout === "" && exitCode \!== 0, misattributing the empty output to ENOSPC. The harness kills the process — commands chained after the failure (;, ||, etc.) never execute.

Observed in the wild on a simple ls | grep:

Command output was lost: the temp filesystem at /private/tmp/claude-501/... is full (0MB free).
The child process's stdout/stderr writes failed with ENOSPC.

Suggested fix: add a bsize <= 0n guard alongside the existing negative check:

if (q < 0n || A.bsize <= 0n) return null;

Workaround for other macOS x86_64 users:

The BASH_ENV variable tells bash to source a file before running non-interactive commands (which is how Claude Code spawns them). Set a trap that ensures stdout is never empty on failure:

  1. Create ~/.claude/enospc-workaround.sh:
trap '[ $? -ne 0 ] && printf "\n"' EXIT
  1. Add to ~/.bashrc:
export BASH_ENV="$HOME/.claude/enospc-workaround.sh"
  1. Restart Claude Code (new terminal).

This has zero impact on successful commands. Remove once Claude Code ships with Bun >= 1.3.15.

atdr · 2 months ago

Building on @maneeshjupiter's BASH_ENV workaround in https://github.com/anthropics/claude-code/issues/63877#issuecomment-4627467164: on stock macOS the trap alone does not work, because a second bug prevents the EXIT trap from ever firing. One extra line makes the workaround reliable. Findings below, verified on 2.1.172 and 2.1.173 (both still embed bun-v1.3.14) on macOS x86_64.

Second bug: snapshot shadow functions exec over the wrapper shell on bash 3.2

The shell snapshot (~/.claude/shell-snapshots/snapshot-bash-*.sh) shadows grep, find, and rg with functions dispatching to the embedded ugrep/bfs. Subshell detection looks like:

elif [[ $BASHPID != $$ ]]; then
  exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I ... "$@"
else
  (exec -a ugrep "$_cc_bin" -G ... "$@")
fi

BASHPID was added in bash 4.0. macOS /bin/bash is 3.2.57, where it is empty, so [[ $BASHPID != $$ ]] is always true and every grep/find/rg call takes the exec branch, replacing the main wrapper shell. Consequences:

  1. All traps die, so any BASH_ENV EXIT trap never runs.
  2. Commands chained after a grep/find (grep foo f && next, grep foo f; next) are silently never executed.
  3. The harness epilogue (pwd -P >| /tmp/claude-*-cwd) never runs, so cwd tracking breaks.
  4. A grep with no matches then exits 1 with empty stdout, which is exactly the trigger for the Bun bsize=0 false ENOSPC ("temp filesystem is full (0MB free)") described in the comment above.

Pipelines like grep foo f | head survive because the exec happens inside a pipeline subshell, which makes the failures look intermittent.

Updated workaround

On bash 3.2, BASHPID is an ordinary assignable variable (it only became special and readonly in 4.0), so it can be supplied from BASH_ENV. Full ~/.claude/enospc-workaround.sh:

# bash 3.2 has no BASHPID; defining it steers Claude Code's grep/find/rg
# shadow functions onto their safe subshell branch instead of exec'ing over
# the wrapper shell. No-op on bash >= 4, where BASHPID is set and readonly.
[ -z "${BASHPID:-}" ] && BASHPID=$$

# Bun 1.3.14 statfs bsize=0 bug (oven-sh/bun#31133): ensure stdout is never
# empty on failure so the false ENOSPC preflight does not fire.
trap '[ $? -ne 0 ] && printf "\n"' EXIT

with export BASH_ENV="$HOME/.claude/enospc-workaround.sh" in ~/.bashrc as before. Result: no more false ENOSPC, chained commands execute again, and the embedded ugrep/bfs still work, since the shadow functions simply take their existing (exec ...) subshell branch.

Suggested fixes in Claude Code

  1. Guard the subshell test in the snapshot template, e.g. [[ -n $BASHPID && $BASHPID != $$ ]], or drop the exec fast path entirely; the subshell branch is always correct.
  2. Keep the suggested preflight guard if (q < 0n || A.bsize <= 0n) return null; and/or ship Bun >= 1.3.15.

Side note for affected users: the shadowing is skipped when Grep or Glob is passed via --allowedTools/--tools at launch (it sets searchToolsOptIn), which is another temporary escape hatch that avoids the exec bug entirely (though not the Bun preflight one).

github-actions[bot] · 17 days ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.