Sandbox: hardcoded deny on .idea/.vscode/.claude/.husky writes has no user override
Problem
sandbox-runtime injects an unconditional (deny file-write* …) rule for every entry in an internal list of tooling-config directories — .git, .vscode, .idea, .claude, .husky, plus .claude/commands, .claude/agents, .git/hooks, and .git/config. The denies are emitted after the user's allowWrite rules, so on macOS sandbox-exec (last-matching-rule wins) they always override.
There's a narrow opt-out for .git/config via allowGitConfig, but no equivalent for the other entries — and allowGitConfig doesn't appear to be exposed in the settings.json schema either.
Reproduction
With this minimal ~/.claude/sandbox-settings.json:
{
"sandbox": { "enabled": true, "failIfUnavailable": true,
"filesystem": { "allowWrite": ["~/projects/"] } },
"permissions": { "defaultMode": "bypassPermissions" }
}
Then:
claude --settings ~/.claude/sandbox-settings.json -p 'bash: echo a > ~/projects/anywhere/foo.txt' # ok
claude --settings ~/.claude/sandbox-settings.json -p 'bash: echo a > ~/projects/anywhere/.idea/foo.txt' # FAIL: Operation not permitted
Outside the sandbox, both succeed. Under a hand-written sandbox-exec profile that grants (allow file-write* (subpath "/Users/me/projects")), both also succeed. So this isn't an OS-level restriction — it's specifically sandbox-runtime's hardcoded deny list.
Why it matters
- I have a custom
review-prskill that copiesprimary/.idea/into newly-created git worktrees so IntelliJ opens each checkout pre-configured with the right modules, SDKs, and run configs. Underclaude-sbthis is now a no-op, defeating the integration. - Anyone running an IntelliJ project under sandbox can't have Claude touch
.idea/even when they've explicitly opted into~/projects/writes. - Same hits
.vscode/(settings.json,launch.json) and.husky/hook scripts.
Proposed fix
Expose an undeny knob in the JSON schema, e.g.:
{
"sandbox": {
"filesystem": {
"allowWrite": ["~/projects/"],
"allowWriteWithinDeny": ["**/.idea/**", "**/.vscode/**"]
}
}
}
Generated rules would be emitted after the deny block so sandbox-exec's last-match-wins picks them up. (allowGitConfig is the existing precedent, just narrower.)
Investigation notes
Relevant code paths in the bundled sandbox-runtime: xD5() builds the deny list from an internal array (containing .git, .vscode, .idea, .claude, .husky) plus .claude/commands, .claude/agents, .git/hooks, and .git/config (allowGitConfig-gated). pD5(_, J, q) concatenates H.denyWithinAllow ?? [] with xD5(q) and emits all of them as (deny file-write* …) after the allows. No code path consults a user-provided override list for these specifically.
Claude Code 2.1.150, darwin 25.5.0 (arm64).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗