sandbox.enabled leaves $TMPDIR read-only when CLAUDE_CODE_TMPDIR is set
sandbox.enabled leaves $TMPDIR read-only when CLAUDE_CODE_TMPDIR is set
Version: Claude Code 2.1.263
Platform: Linux (Debian trixie), bubblewrap 0.12.0
Settings: "sandbox": { "enabled": true, "failIfUnavailable": true }
Summary
When CLAUDE_CODE_TMPDIR points at a directory under $HOME, a sandboxed
session sets $TMPDIR to that directory and then denies writes to it. Anything
using mktemp fails.
The sandbox's own guidance to the model says the opposite:
For temporary files, always use the $TMPDIR environment variable. TMPDIR is automatically set to the correct sandbox-writable directory in sandbox mode.
Reproduce
export CLAUDE_CODE_TMPDIR="$HOME/.local/state/scratch"(any directory under
$HOME outside the workspace)
- Add to settings:
"sandbox": { "enabled": true, "failIfUnavailable": true } - Start a session and run
mktemp -d
Observed:
mktemp: failed to create directory via template
'/home/<user>/.local/state/scratch/tmp.XXXXXX': Read-only file system
echo $TMPDIR inside the session prints that same directory.
Probable cause
The sandbox's reported write allowlist contains the literal token "$TMPDIR"
alongside fully expanded absolute paths:
"write": { "allowOnly": [ "/dev/stdout", ..., "/home/<user>/.npm/_logs",
".", "$TMPDIR" ] }
That is consistent with the variable never being expanded, and therefore never
matching the path it is supposed to permit.
Knock-on effects
- Any tooling that calls
mktempfails. In my case an entire shell test suite
went red, with the failure surfacing as unrelated errors once $TMP was empty
and paths collapsed to /.
- Nested sessions cannot start. A child
claudeprocess cannot bind its
control socket in that directory:
````
Sandbox is required but failed to initialize: EPERM: operation not permitted,
listen '/home/<user>/.local/state/scratch/srt-mux-6-1.sock'. Restart to retry.
With failIfUnavailable: true it correctly refuses to run rather than
continuing unsandboxed — that part works exactly as documented.
ghfails withHTTP 401in the same session, which I did not chase further.
Suggested fix
Expand $TMPDIR when building the write allowlist, or resolve the sandbox's
temp root independently of CLAUDE_CODE_TMPDIR and set $TMPDIR to whatever
that resolves to. Either makes the documented contract true.
Note on recovery
Worth knowing for anyone who hits this: the setting cannot be reverted from
inside a session, because writing the updated settings file requires writing
under ~/.claude, which the sandbox denies. Restarting does not help either —
the live settings still enable it. The revert has to be made from a shell outside
Claude Code.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗