[BUG] `--settings` flag resolves sandbox filesystem paths relative to file, not project root

Resolved 💬 2 comments Opened Apr 10, 2026 by ozydingo Closed May 24, 2026

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 ~/.claude for 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

  1. Create .claude/settings.json:
{
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "failIfUnavailable": true,
    "autoAllowBashIfSandboxed": true,
    "filesystem": {
      "denyWrite": ["./foo"]
    }
  }
}
  1. Create .claude/sandbox.json
{
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "failIfUnavailable": true,
    "autoAllowBashIfSandboxed": true,
    "filesystem": {
      "denyWrite": ["./bar", "../baz"]
    }
  }
}
  1. Run:
mkdir ./{foo,bar,baz}
  1. Run:
claude --settings .claude/sandbox.json -p 'Run `touch bar/file`'

Observe: write succeeds, despite ./bar being in the denyWrite list.

  1. Run:
claude --settings .claude/sandbox.json -p 'Run `touch foo/file`'

Observe: write is blocked, as expected.

  1. 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_

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗