Fix/macos per session tmpdir#90
Merged
Merged
Conversation
Replace the static /tmp/greywall TMPDIR with a unique per-session directory created by os.MkdirTemp at sandbox init time. The dynamic path is added to the Seatbelt write allowlist and injected as TMPDIR into the sandboxed environment, then removed on Cleanup(). This fixes bash commands being blocked under Claude Code on macOS: the shell was falling back to /private/tmp when TMPDIR pointed at the non-existent /tmp/greywall, and the sandbox denied those writes. The per-session dir lives under the system TMPDIR (/var/folders/...), which is already covered by the getTmpdirParent() write rule. Closes #11
Claude Code constructs /private/tmp/claude-{uid}/{cwd} for its bash
tool's working directory, ignoring $TMPDIR. Add that UID-scoped path
to the darwin read/write allowlists in the built-in claude profile so
bash commands (echo, ls, etc.) are no longer blocked by EPERM.
…rofile
Claude Code uses two distinct temp path patterns on macOS:
- /private/tmp/claude-{uid}/{encoded-cwd} (session directory, UID-based)
- /tmp/claude-{pid}-cwd (working-dir tracker, PID-based)
The previous UID-specific paths didn't cover the PID-based tracker, causing
`zsh: operation not permitted: /tmp/claude-{pid}-cwd` on every bash command.
Replace with glob patterns /tmp/claude-* and /tmp/claude-*/** which cover
both. expandMacOSTmpPaths mirrors these to /private/tmp/ automatically.
b981d52 to
3bf14a7
Compare
tito
approved these changes
May 20, 2026
tito
pushed a commit
that referenced
this pull request
May 20, 2026
* fix(macos): per-session TMPDIR for sandboxed processes Replace the static /tmp/greywall TMPDIR with a unique per-session directory created by os.MkdirTemp at sandbox init time. The dynamic path is added to the Seatbelt write allowlist and injected as TMPDIR into the sandboxed environment, then removed on Cleanup(). This fixes bash commands being blocked under Claude Code on macOS: the shell was falling back to /private/tmp when TMPDIR pointed at the non-existent /tmp/greywall, and the sandbox denied those writes. The per-session dir lives under the system TMPDIR (/var/folders/...), which is already covered by the getTmpdirParent() write rule. Closes #11 * fix(macos): allow Claude Code bash working dir in Seatbelt profile Claude Code constructs /private/tmp/claude-{uid}/{cwd} for its bash tool's working directory, ignoring $TMPDIR. Add that UID-scoped path to the darwin read/write allowlists in the built-in claude profile so bash commands (echo, ls, etc.) are no longer blocked by EPERM. * fix(macos): use glob patterns for Claude Code tmp paths in Seatbelt profile Claude Code uses two distinct temp path patterns on macOS: - /private/tmp/claude-{uid}/{encoded-cwd} (session directory, UID-based) - /tmp/claude-{pid}-cwd (working-dir tracker, PID-based) The previous UID-specific paths didn't cover the PID-based tracker, causing `zsh: operation not permitted: /tmp/claude-{pid}-cwd` on every bash command. Replace with glob patterns /tmp/claude-* and /tmp/claude-*/** which cover both. expandMacOSTmpPaths mirrors these to /private/tmp/ automatically.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(macos): per-session TMPDIR and Claude Code bash working-dir access
Closes #11
Problem
On macOS, sandboxed processes were silently failing to write to
/tmpbecausethe static path
greywallinjected (TMPDIR=/tmp/greywall) was never actuallycreated, so processes fell back to
/private/tmp— which the Seatbelt policydenied.
For the
claudeprofile specifically this caused two distinct failures:Session directory blocked — Claude Code hardcodes
/private/tmp/claude-{uid}/{encoded-cwd}as its bash session directory,ignoring
$TMPDIRentirely. Any bash tool call resulted in:Working-dir tracker blocked — Claude Code also creates
/tmp/claude-{pid}-cwdto persist the working directory between commands.The PID suffix changes every run, so a UID-specific allow rule wasn't enough:
Fix
Per-session TMPDIR (
manager.go,macos.go,utils.go,dangerous.go)Instead of injecting a static
TMPDIRpath that may not exist,Manager.Initialize()now calls
os.MkdirTemp("", "greywall-")on macOS. This creates a real directoryinside the system's
$TMPDIR(/var/folders/.../T/greywall-XXXXXX), which isalready covered by the existing
getTmpdirParent()write rule. The path is:allow file-write*rulesTMPDIR=...in the sandboxed process environmentos.RemoveAllinManager.Cleanup()The old static
/tmp/greywallpaths are removed fromGetDefaultWritePaths().Claude Code glob allowlist (
profiles/agents/claude.go)Added glob patterns to the
claude/claude-codeprofile's darwin allowlist:expandMacOSTmpPathsinmacos.goautomatically mirrors these to/private/tmp/claude-*and/private/tmp/claude-*/**, so both the canonicalpath and the symlink target are covered.
Testing
make test)TestMacOS_SeatbeltAllowsTmpGreywallreplaced withTestMacOS_SeatbeltAllowsPerSessionTmpDir(tests$TMPDIRwrite via theactual sandbox, not a static path)
greywall --profile claude -- claude --dangerously-skip-permissions:bash tool calls (
echo hello) now complete cleanly with no permission errors