Write tool does not enforce the working-directory sandbox that Bash enforces

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 19, 2026

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

  • claude CLI (non-interactive -p mode)
  • macOS (Darwin arm64)
  • Reproduced with --model claude-haiku-4-5, --permission-mode acceptEdits, and a settings.json allow-listing Read, Grep, Glob, Edit, Write and Bash(git:*) among others (no explicit deny/allow for arbitrary paths)

Steps to reproduce

  1. Create a git worktree as a subdirectory of a larger project, e.g. <repo>/.worktrees/foo inside <repo>.
  2. Invoke claude -p with cwd set to the worktree via a subshell (( cd <repo>/.worktrees/foo && exec claude -p "..." --settings settings.json --permission-mode acceptEdits )), granting Write (and not granting --add-dir for anything outside the worktree).
  3. 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 the Bash tool (echo x > <repo>/MARKER.txt), then via the Write tool 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

  • Bash write to the out-of-bounds path: denied — "blocked by security restriction to working directory" (or similar wording reported by the model).
  • Write tool 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.

View original on GitHub ↗