You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Confine Claude write lanes with an OS sandbox that also excludes a linked worktree's shared .git #118
isolated-write Claude lanes run headless with --permission-mode acceptEdits and no OS sandbox. Under acceptEdits, headless Claude refuses Bash commands that need approval, such as a python3 file write or most git commands. So a Claude implementer driven from a Codex parent cannot run much of its own verification. Grok write lanes (after #116) and Codex write lanes run shell commands inside an OS sandbox.
auto permission mode is not confinement. A Sonnet lane ran echo probe > ~/projects/escape-probe-OUTSIDE.txt from its worktree with 0 permission denials. Haiku 4.5 also starts in default mode under auto.
acceptEdits plus the Bash sandbox works:
The settings are --settings '{"sandbox":{"enabled":true,"autoAllowBashIfSandboxed":true,"allowUnsandboxedCommands":false,"failIfUnavailable":true}}'.
Commands run without prompts, and absolute or sibling writes fail with "Operation not permitted" on Opus, Sonnet and Haiku.
A dangerouslyDisableSandbox retry fails, and repository settings cannot loosen it.
There is no network: the proxy starts with an empty allowlist.
The remaining gap is the shared .git. In a linked worktree, Claude Code's sandbox deliberately allows writes to the main repository's shared .git, except hooks/ and config. A writer can therefore change shared refs, the primary index or the shared stash, for example with git stash pop. Grok's workspace sandbox refuses the same writes: git branch fails with "cannot lock ref". No pstack skill asks writer lanes to commit or stash.
The work so far is on branch claude/claude-writer-sandbox, at 469ea9e.
Done when
A Claude write lane runs approval-needing commands without prompts, inside an OS-enforced sandbox.
It cannot write outside its worktree, including the linked worktree's shared .git.
Step-back after head 3 of #122 (advisor: Astra, gpt-6-astra high; three messages)
Why the previous plan did not converge. Each repair turned one pathname or environment case into protection, while the design treated protection as optional. When discovery failed, or when a path could not be expressed as a deny, the runner went on without the deny, and auto-approval then widened access. Every round found another such case: glob characters, newlines in a path, inherited GIT_* routing, and Linux dropping wildcard entries.
Class rule (the new design): the runner either passes a literal shared-.git deny path that it resolved exactly, or it refuses the Claude writer lane before launch. It never runs a writer whose shared .git it could not protect.
Fail-closed discovery.
No .git anywhere up from the cwd means no repository and no deny.
A .git whose gitdir or commondir cannot be resolved exactly (unreadable, dangling, malformed) means the lane is refused.
gitdir is parsed as git writes it, so newlines in the path are kept.
One routing context. Claude isolated-write lanes get no inherited GIT_* variables.
Literal deny only.denyWritePattern and its ? substitution are deleted. A path containing *, ?, [, ], {, } or a control character means the lane is refused, because the Linux 2.1.288 policy builder drops wildcard entries.
Fixture hooks. The test fixture's git calls disable hooks.
Product calls, made under Eric's 2026-10-04 overnight mandate:
Supported boundary. The verified platform is macOS with Claude Code 2.1.288. On Linux, the refusal rule covers wildcard paths, and literal-path confinement is stated as unverified because no Linux host is available.
Trust. Project hooks and file-tool allowances in the user's own worktree are trusted input, as on main. The lane guarantee is Bash confinement plus the shared-.git deny.
Measured in the live gate, not assumed:
Write and Edit tool attempts on ../outside and on <main>/.git/refs, recorded from the tool calls.
Ref, index and object mutations under an ordinary path and under the sandbox temp tree.
Refusal before model execution for a [-path and for a corrupt gitdir, with the parent-visible exit code and message.
GIT_DIR set in the parent.
Dissent recorded: Astra holds that macOS evidence does not validate accepted Linux paths. I agree, and that point stays listed as unverified.
Reopening conditions: a confirmed Claude Code version change to sandbox policy compilation, or any writer reaching shared refs in a supported layout.
Activity
fix(runner): find the shared git directory without spawning git (#118)
fix(runner): match glob characters in the shared git path literally (#…
ericlitman commentedon Oct 4, 2026
Step-back after head 3 of #122 (advisor: Astra, gpt-6-astra high; three messages)
Why the previous plan did not converge. Each repair turned one pathname or environment case into protection, while the design treated protection as optional. When discovery failed, or when a path could not be expressed as a deny, the runner went on without the deny, and auto-approval then widened access. Every round found another such case: glob characters, newlines in a path, inherited
GIT_*routing, and Linux dropping wildcard entries.Class rule (the new design): the runner either passes a literal shared-
.gitdeny path that it resolved exactly, or it refuses the Claude writer lane before launch. It never runs a writer whose shared.gitit could not protect..gitanywhere up from the cwd means no repository and no deny..gitwhosegitdirorcommondircannot be resolved exactly (unreadable, dangling, malformed) means the lane is refused.gitdiris parsed as git writes it, so newlines in the path are kept.GIT_*variables.denyWritePatternand its?substitution are deleted. A path containing*,?,[,],{,}or a control character means the lane is refused, because the Linux 2.1.288 policy builder drops wildcard entries.Product calls, made under Eric's 2026-10-04 overnight mandate:
main. The lane guarantee is Bash confinement plus the shared-.gitdeny.Measured in the live gate, not assumed:
../outsideand on<main>/.git/refs, recorded from the tool calls.[-path and for a corruptgitdir, with the parent-visible exit code and message.GIT_DIRset in the parent.Dissent recorded: Astra holds that macOS evidence does not validate accepted Linux paths. I agree, and that point stays listed as unverified.
Reopening conditions: a confirmed Claude Code version change to sandbox policy compilation, or any writer reaching shared refs in a supported layout.
fix(runner): refuse a Claude writer whose shared .git cannot be denie…
fix(runner): let git find the shared git directory inside the lane li…
fix(runner): run git discovery in the C locale (#118)
fix(runner): confine Claude write lanes with the Bash sandbox and den…