Reducing permission prompts in my bash sandbox
I wrote about how I’ve configured Claude Code’s Bash Sandbox a while ago, and it’s generally been working pretty well, except for two things:
- Claude loves to run composite commands like
git add . && git push, which trigger a permission prompt for the whole thing because thegit pushrequires it. - Claude loves to pipe the output of expensive commands like
bun run buildintogrepto look for errors and the like. If the grep is too tight, it needs to rerun the command.
I’ve addressed both of these and a few other things by adding this to my global CLAUDE.md.
Bash
- Look at what commands actually print. Long output is saved to a file you can grep or tail afterwards, so don't pre-filter through
head/tail/grepor wrap commands in scripts that printok/FAILby default: that throws away the detail you need when something goes wrong, and a pipe hides the exit code. This matters most for slow commands (builds, full test suites) you can't cheaply rerun: run them bare or redirect to$TMPDIR/<name>.log 2>&1; echo "exit $?", then search the log. Filtering is fine when you know exactly what you want, likegit log --oneline | head. - Prefer several small Bash calls to one long chain. Each gets its own output and exit status, and independent ones can run in parallel.
Sandbox
Bash runs in a Seatbelt sandbox, usually in auto mode. The aim of this is:
- You can work freely within the session's scope without needing my constant input via permissions prompts.
- I'm prompted to authorise actions I want to oversee, which are mostly: A. writing/affecting external things (eg. pushes, writing to other projects, leaving github comments); B. Accessing potentially sensitive stuff (eg. reading certain local dirs); C. Running things outside the sandbox (eg. opening apps, touching
~/notes).
How it works:
- Sandboxed commands can write to the working dir, caches and
$TMPDIR, and reach allowlisted hosts. They can't read credential dirs (~/.ssh,~/.config/gh…),~/Library, personal folders or~/notes(unless it's the working dir). That's deliberate: if a task genuinely needs one of these, ask viadangerouslyDisableSandbox(below) rather than finding another route, and never for credential dirs unless I ask. - Some commands (eg.
gh,git push/pull/fetch,tdn,docker,defuddle,ps,pgrep,lsof,kill,echo,sleep) are excluded from the sandbox: they run outside it under my normal permission rules, so read-only ones usually don't prompt and ones inpermissions.askdo. That only happens when every command in the call is excluded and there's nocd, file redirect,$(...), heredoc, subshell or loop (2>&1is fine). So run them bare: use gh's--jq/--template/--limitinstead of piping tohead/grep/jq(long output is saved to a file you can search),--repo owner/nameinstead ofcd, and--body-fileinstead of--body "$(cat <<EOF)". - Put anything that needs my approval (
git push,gh pr create, anything inpermissions.ask) in its own Bash call, so the rest runs without me: eg add + commit in one call, push in the next. dangerouslyDisableSandboxalways prompts me. If a command needs something the sandbox blocks (a path listed above, writing outside the working dir, other apps), don't hunt for workarounds: run it withdangerouslyDisableSandboxstraight away so I get one clear prompt. Keep that call to just the command that needs it, since the whole call runs unsandboxed. Don't use it for things that work in the sandbox, or on the excluded commands above.- If something fails oddly (EPERM under
~/Library, a project-local cache, a different binary on PATH), suspect the sandbox and tell me before trying workarounds. - Unless a project's own tools say otherwise, change file contents with Edit/Write: they show me a diff, fail loudly on a mismatch and work with
/rewind. Bash (sed etc.) is fine for mechanical bulk changes across many files and for moving files; don't write Python scripts to edit files unless you have a compelling reason to do so.
## Bash
- Look at what commands actually print. Long output is saved to a file you can grep or tail afterwards, so don't pre-filter through `head`/`tail`/`grep` or wrap commands in scripts that print `ok`/`FAIL` by default: that throws away the detail you need when something goes wrong, and a pipe hides the exit code. This matters most for slow commands (builds, full test suites) you can't cheaply rerun: run them bare or redirect to `$TMPDIR/<name>.log 2>&1; echo "exit $?"`, then search the log. Filtering is fine when you know exactly what you want, like `git log --oneline | head`.- Prefer several small Bash calls to one long chain. Each gets its own output and exit status, and independent ones can run in parallel.
### Sandbox
Bash runs in a Seatbelt sandbox, usually in auto mode. The aim of this is:
1. You can work freely within the session's scope without needing my constant input via permissions prompts.2. I'm prompted to authorise actions I want to oversee, which are mostly: A. writing/affecting external things (eg. pushes, writing to other projects, leaving github comments); B. Accessing *potentially sensitive stuff* (eg. reading certain local dirs); C. Running things outside the sandbox (eg. opening apps, touching `~/notes`).
How it works:
- Sandboxed commands can write to the working dir, caches and `$TMPDIR`, and reach allowlisted hosts. They can't read credential dirs (`~/.ssh`, `~/.config/gh`…), `~/Library`, personal folders or `~/notes` (unless it's the working dir). That's deliberate: if a task genuinely needs one of these, ask via `dangerouslyDisableSandbox` (below) rather than finding another route, and never for credential dirs unless I ask.- Some commands (eg. `gh`, `git push/pull/fetch`, `tdn`, `docker`, `defuddle`, `ps`, `pgrep`, `lsof`, `kill`, `echo`, `sleep`) are excluded from the sandbox: they run outside it under my normal permission rules, so read-only ones usually don't prompt and ones in `permissions.ask` do. That only happens when every command in the call is excluded and there's no `cd`, file redirect, `$(...)`, heredoc, subshell or loop (`2>&1` is fine). So run them bare: use gh's `--jq`/`--template`/`--limit` instead of piping to `head`/`grep`/`jq` (long output is saved to a file you can search), `--repo owner/name` instead of `cd`, and `--body-file` instead of `--body "$(cat <<EOF)"`.- Put anything that needs my approval (`git push`, `gh pr create`, anything in `permissions.ask`) in its own Bash call, so the rest runs without me: eg add + commit in one call, push in the next.- `dangerouslyDisableSandbox` always prompts me. If a command needs something the sandbox blocks (a path listed above, writing outside the working dir, other apps), don't hunt for workarounds: run it with `dangerouslyDisableSandbox` straight away so I get one clear prompt. Keep that call to just the command that needs it, since the whole call runs unsandboxed. Don't use it for things that work in the sandbox, or on the excluded commands above.- If something fails oddly (EPERM under `~/Library`, a project-local cache, a different binary on PATH), suspect the sandbox and tell me before trying workarounds.- Unless a project's own tools say otherwise, change file contents with Edit/Write: they show me a diff, fail loudly on a mismatch and work with `/rewind`. Bash (sed etc.) is fine for mechanical bulk changes across many files and for moving files; don't write Python scripts to edit files unless you have a compelling reason to do so.This seems to be working pretty well.