[BUG] `--settings` flag resolves sandbox filesystem paths relative to file, not project 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?
sandbox.filesystem.denyWrite paths loaded via the --settings CLI flag resolve relative to the settings file's directory, not the project root. E.g. when using claude --settings ./claude/sandbox.json, filesystem.denyWrite: ["./spec"] does not block writes to <project_root>/spec, but filesystem.denyWrite: ["../spec"] does. This is inconsistent with how the same paths resolve in .claude/settings.json` (project scope) and contradicts the documented behavior.
The sandboxing docs define path resolution by scope:
./or no prefix: Relative to the project root for project settings, or to~/.claudefor user settings.
There is no documented third behavior for --settings files. The --settings loader appears to use naive file-relative resolution (e.g., path.resolve(dirname(settingsFile), relativePath)) rather than the documented scope-based resolution used by the standard settings hierarchy.
The documentation for --settings states only "Path to a settings JSON file or a JSON string to load additional settings from", and it is a reasonable conclusion and design that settings from these files are merged with standard settings without resolution differences based on the settings file path.
What Should Happen?
Additional settings added via --settings flag to claude CLI should follow the same path resolution rules as the standard settings file.
Error Messages/Logs
Steps to Reproduce
- Create
.claude/settings.json:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"denyWrite": ["./foo"]
}
}
}
- Create
.claude/sandbox.json
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"denyWrite": ["./bar", "../baz"]
}
}
}
- Run:
mkdir ./{foo,bar,baz}
- Run:
claude --settings .claude/sandbox.json -p 'Run `touch bar/file`'
Observe: write succeeds, despite ./bar being in the denyWrite list.
- Run:
claude --settings .claude/sandbox.json -p 'Run `touch foo/file`'
Observe: write is blocked, as expected.
- Run:
claude --settings .claude/sandbox.json -p 'Run `touch baz/file`'
Observe: write is blocked. ../baz resolves from .claude/ up to <project-root>/baz (file-relative resolution in the --settings code path, undocumented and unexpected). The same ./ prefix in./foo from .claude/settings.json resolves to <project-root>/foo (project-root-relative, per docs).
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.100
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗