sandbox.enabled leaves $TMPDIR read-only when CLAUDE_CODE_TMPDIR is set

Status Open
Reported on v2.1.263
Maintainer reply None cached
Activity 2 comments · opened Sep 7, 2026

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

  1. export CLAUDE_CODE_TMPDIR="$HOME/.local/state/scratch" (any directory under

$HOME outside the workspace)

  1. Add to settings: "sandbox": { "enabled": true, "failIfUnavailable": true }
  2. 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 mktemp fails. 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 claude process 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.

  • gh fails with HTTP 401 in 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.

View original on GitHub ↗

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