[BUG] TMPDIR not set in sandbox mode if bubblewrap is installed setuid root
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The TMPDIR environment variable is not set in sandboxed bash commands, despite issue #8194 documenting that it should be automatically set to /tmp/claude when running in sandbox mode. This causes sandbox failures when commands like bats are run using the Bash tool in sandboxing mode, leading to many unnecessary retries with it set inside the call.
Observable behavior:
/tmp/claudedirectory is created by Claude Code ✓- Sandbox mode is active (
CLAUDE_CODE_SANDBOX=1is set) ✓ TMPDIRenvironment variable is not set ✗
This causes programs that rely on TMPDIR to fall back to /tmp instead of using the sandboxed /tmp/claude/ directory.
More generally, TMPDIR not being set inside the Bash sandbox tool environment should be detected and flagged, as it explicitly is set to /tmp/claude.. Alternately warn if bwrap is set setuid, as other env variables might not be passed on. Flatpak handled that explicitly for example (see CVE-2021-21261 and below).
What Should Happen?
According to issue #8194:
"When running in sandbox mode, a temporary directory at
/tmp/claude/is made available. TheTMPDIRenvironment variable is automatically set to this path, and most programs that respect TMPDIR will automatically use/tmp/claude/"
The TMPDIR environment variable should be set to /tmp/claude in all sandboxed bash commands.
Error Messages/Logs
$ env | grep TMPDIR
# (no output - TMPDIR is unset)
$ env | grep CLAUDE_CODE_SANDBOX
CLAUDE_CODE_SANDBOX=1
$ ls -la /tmp/claude
drwxr-xr-x addy addy 4.0 KB Tue Nov 4 01:19:00 2025 /tmp/claude
Steps to Reproduce
Quick verification:
- Run Claude Code on NixOS with gamescope/Steam configured (or any system with setuid bwrap)
- Execute any bash command in sandbox mode
- Check
env | grep TMPDIR- it will be unset
Root cause verification (Docker repro):
I've created a minimal Dockerfile that reproduces this issue. It demonstrates that when bwrap is setuid root (as it is on NixOS with gamescope), glibc filters TMPDIR from the environment per the UNSECURE_ENVVARS security policy.
The repro shows:
- Non-setuid bwrap: TMPDIR inherits ✓
- Setuid bwrap as root: TMPDIR inherits ✓
- Setuid bwrap as non-root: TMPDIR filtered ✗ ← The bug
- With
--setenv TMPDIR: Works ✓ ← The fix
FROM archlinux:base-20251019.0.436919
RUN pacman -Syu --noconfirm && \
pacman -S --noconfirm bubblewrap zsh && \
pacman -Scc --noconfirm && \
useradd -m demo && \
chmod u+s /usr/bin/bwrap
RUN cat > /test.sh << 'EOF'
#!/bin/bash
echo "=== Non-setuid bwrap ==="
chmod 755 /usr/bin/bwrap
TMPDIR=/tmp/test bwrap --bind / / --dev /dev -- zsh -c 'echo "TMPDIR: ${TMPDIR:-unset}"'
echo -e "\n=== Setuid bwrap (as root) ==="
chmod u+s /usr/bin/bwrap
TMPDIR=/tmp/test bwrap --bind / / --dev /dev -- zsh -c 'echo "TMPDIR: ${TMPDIR:-unset}"'
echo -e "\n=== Setuid bwrap (as non-root) - THE BUG ==="
su demo -c 'TMPDIR=/tmp/test bwrap --bind / / --dev /dev -- zsh -c "echo \"TMPDIR: \${TMPDIR:-unset}\""'
echo -e "\n=== With --setenv TMPDIR - THE FIX ==="
su demo -c 'TMPDIR=/tmp/test bwrap --bind / / --dev /dev --setenv TMPDIR /tmp/claude -- zsh -c "echo \"TMPDIR: \${TMPDIR:-unset}\""'
EOF
RUN chmod +x /test.sh
CMD ["/test.sh"]
Quick Run
docker build -f Dockerfile.bwrap-tmpdir-demo -t bwrap-tmpdir-demo .
docker run --rm --privileged bwrap-tmpdir-demo
Expected Output
=== Non-setuid bwrap ===
TMPDIR: /tmp/test
=== Setuid bwrap (as root) ===
TMPDIR: /tmp/test
=== Setuid bwrap (as non-root) - THE BUG ===
TMPDIR: unset
=== With --setenv TMPDIR - THE FIX ===
TMPDIR: /tmp/claude
What This Proves
When bwrap is setuid and run by a non-root user, glibc filters TMPDIR.
The fix: Claude Code must pass --setenv TMPDIR /tmp/claude to bwrap.
Technical background:
Claude Code uses bubblewrap (bwrap) for Linux sandboxing. When bwrap is installed setuid root (common on NixOS and other distros for privilege separation), glibc automatically filters environment variables listed in UNSECURE_ENVVARS, which includes TMPDIR.
Reference: https://sourceware.org/git/?p=glibc.git&a=blob&f=sysdeps/generic/unsecvars.h
Claude Code already uses bwrap's --setenv flag for variables like CLAUDE_CODE_SANDBOX=1. The fix is to also pass --setenv TMPDIR /tmp/claude to explicitly set TMPDIR, bypassing glibc's filtering.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
No response
Claude Code Version
2.0.32
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
Affected systems:
- NixOS with gamescope/Steam (creates setuid bwrap wrapper)
- Any Linux distribution where bwrap is installed setuid
- Not affected: Systems where bwrap is not setuid
Workaround: Add a non setuid bwrap executable early in PATH before launching claude-code.
Related issues:
- #8194 - Documents expected TMPDIR behavior (this issue reports it's not working)
- #10194 - Different issue (user-set invalid TMPDIR causing failures)
Related Issues & Documentation
Flatpak encountered and fixed this exact issue:
- flatpak/flatpak@6d1773d - "Convert all environment variables into bwrap arguments" (CVE-2021-21261 fix)
- flatpak/flatpak#4081 - "More consistently convert bwrap->envp into --setenv arguments" (regression fix for setuid bwrap)
Glibc documentation:
- Unix & Linux Stack Exchange - Explains glibc's ld.so filters TMPDIR for setuid binaries
- Server Fault - "glibc will remove certain environment variables when running setuid programs"
Other affected software:
- Wireshark dumpcap issue - Wireshark's dumpcap hits same TMPDIR filtering issue
NixOS context:
- nixpkgs/nixos/modules/programs/steam.nix - Shows why NixOS makes bwrap setuid (for gamescope+Steam)
Leaving here this workaround for ppl trying to use the agent sdk
Set:
query({
options: {
env: {
...process.env,
XDG_CACHE_DIR: "/tmp/claude",
XDG_CACHE_HOME: "/tmp/claude",
TMPDIR: "/tmp/claude",
},
...