Write tool does not enforce the working-directory sandbox that Bash enforces
Summary
When Claude Code is invoked with a working directory restriction (e.g. claude -p from a cd <dir> && exec claude ... subshell, with a --settings file granting Write), the Bash tool correctly refuses to write outside that directory, but the Write tool does not — it writes to arbitrary paths on the filesystem regardless of the working-directory boundary that Bash enforces.
Environment
claudeCLI (non-interactive-pmode)- macOS (Darwin arm64)
- Reproduced with
--model claude-haiku-4-5,--permission-mode acceptEdits, and asettings.jsonallow-listingRead, Grep, Glob, Edit, WriteandBash(git:*)among others (no explicit deny/allow for arbitrary paths)
Steps to reproduce
- Create a git worktree as a subdirectory of a larger project, e.g.
<repo>/.worktrees/fooinside<repo>. - Invoke
claude -pwith cwd set to the worktree via a subshell (( cd <repo>/.worktrees/foo && exec claude -p "..." --settings settings.json --permission-mode acceptEdits )), grantingWrite(and not granting--add-dirfor anything outside the worktree). - Ask the model to write a file at an absolute path outside the worktree — e.g. two directories up, at
<repo>/MARKER.txt— first via theBashtool (echo x > <repo>/MARKER.txt), then via theWritetool targeting the same absolute path.
Expected
Both tool calls are denied — the working-directory boundary should apply uniformly regardless of which tool is used to write the file.
Actual
Bashwrite to the out-of-bounds path: denied — "blocked by security restriction to working directory" (or similar wording reported by the model).Writetool call to the identical out-of-bounds path: succeeds. Verified independently on disk (outside the model's own self-report) — the file is created at the requested out-of-bounds path with the requested content.
Re-tested with every Claude-Code-related environment variable (CLAUDECODE, CLAUDE_CODE_SESSION_ID, CLAUDE_PID, CLAUDE_CODE_CHILD_SESSION, etc.) explicitly unset before invoking claude -p, in case session/parent-process linkage was involved — the Write tool still escaped the working directory identically. So this does not appear to depend on environment inheritance or nested/child-session state; it reproduces from a clean environment.
Why this matters
This was found while debugging an internal automation pipeline that spawns claude -p per-task inside git worktrees, one per task, explicitly to keep each task's file writes isolated to its own worktree — the whole isolation model assumes Bash, Write, and Edit all respect the same working-directory boundary. In a real (non-synthetic) run, a task's Write/Edit calls landed files outside its intended worktree, in a sibling checkout of the same repository that a human had interactively checked out at the time, and a subsequent git commit (via Bash, correctly scoped to whatever directory it was actually invoked from) committed those files there instead of in the intended worktree — producing a duplicate, unintended commit on a branch the automation was never supposed to touch. No data was lost and nothing was pushed in our case, but the mechanism generalizes: any workflow that relies on Write/Edit respecting the same directory sandbox Bash enforces cannot currently assume that.
Suggested fix
Enforce the same working-directory/--add-dir boundary check for Write and Edit (and any other file-mutating tool) that Bash already applies, rather than only gating Bash.