Repository navigation
Description
What is observed
The four tools that write — bash, pwsh, write, edit — sit under one sandbox tier and one set of writable roots. Only two of them can ask for a wider tier for one call.
A call to write or edit whose target lies outside every granted root is refused with a sentence that names the tier and the roots, and nothing else:
read-only sandbox: blocked file write to '/tmp/x/notes.md' — outside every root this tier grants (…)
The model is given no way to ask, and the sentence names no next step. The two shell tools say the opposite in the same situation — they refuse the command and then append retry this exact command once with sandbox_permissions + justification (available here: workspace-write, danger-full-access).
Why that is a problem
The model holds one body of knowledge about "what to do when the boundary stops me", and today that knowledge has two different answers depending on whether the call writes through a file tool or through a shell. A refusal with no exit invites the workaround instead of the question: echo the file from bash, which is the very tool that can ask — so the escalation moves from the front door to the side door, and the host is never told either way.
Consistency is not the aesthetic point. A call that can state why it needs a wider tier should be allowed to say so whether it writes bytes or runs a command, and the host should be the one deciding.
What the fix must satisfy
- The gate keys on the tools that consume the tier and the roots, not on the shell's names. One set, already named in the daemon (
_ROOT_CONSUMING_TOOLS), is the thing that decides who may ask. - The schemas advertise it.
writeandeditdeclaresandbox_permissionsandjustificationwith wording identical to the shell tools and anenumderived from the same single constant — not a second hand-copied list. - The refusal says there is a way. A file refusal that has a wider tier available appends the same sentence the shell refusal appends, reusing
with_retry_hintrather than writing a second phrasing; where no wider tier exists, nothing is appended. - Nothing else moves.
read,globandgrepstay outside the escalation surface (they consume no root, so there is nothing to widen), and the tier and the roots stay daemon-injected — a tool never chooses them. - One call, then back. An approved escalation applies to that call alone; the next call is back at the session's default tier, and a refusal, an unanswered question or a timeout means the call does not run at all.
Acceptance
- A
writeand aneditcall carrying the pair and approved by the host run at the wider tier; the following call runs at the session default. Refused, unanswered and timed-out each leave the call unexecuted. read,globandgrepcarrying the pair ask nothing and behave as before.- All four writing tools declare both parameters identically, and the enum follows the one constant: changing that constant in a tool's own namespace changes that tool's schema, which a hand-copied literal cannot do.
- A file refusal at
read-onlyand atworkspace-writecarries the retry sentence naming the reachable tiers; atdanger-full-accessit carries none. - The existing escalation suite stays green, including the case that pins a tool without a tier cannot escalate.
- CI green on all three platforms.
Activity
argszero commentedon Nov 6, 2026
Handled by #2735
argszero commentedon Nov 6, 2026
Correction to the last acceptance line, so a later reader does not read a pass into it.
The acceptance asks for CI green on all three platforms. This repository's test workflow has two:
.github/workflows/test.ymlrunsubuntu-latestandwindows-2025, andAgent.mdstates the same set. macOS appears only in.github/workflows/build-release.yml(macos-15), which is triggered by a tag and builds, signs and notarizes the package — it does not run this suite.So the readable criterion is: the two test legs green on the PR, plus the macOS leg green on the next tag build that ships this change. A cycle that reports "three platforms green" from a PR page would be reporting a reading that does not exist here, which is the failure this note exists to prevent.
Nothing else in the acceptance changes, and this does not affect the code under review.