Skip to content

fix(runner): pre-approve file edits for headless Grok writers - #84

Draft
smashru wants to merge 9 commits into
ericlitman:mainfrom
smashru:fix/grok-writer-file-edits
Draft

smashru wants to merge 9 commits into
ericlitman:mainfrom
smashru:fix/grok-writer-file-edits

Conversation

@smashru

@smashru smashru commented Sep 23, 2026

Copy link
Copy Markdown

What changed

Headless Grok cancels any tool call that needs an approval prompt, then ends the turn. The runner starts isolated-write Grok lanes with --permission-mode acceptEdits, but on grok CLI 1.0.41, that mode does not pre-approve Grok's file tools. Every write and search_replace call stops at a prompt that headless mode cancels. The runner exits 77 with a permission-cancelled receipt, and the worktree has no changes. A real grok:grok-4.7@xhigh implementation lane ended this way after 38 turns. Its last tool result was "User cancelled the execution for tool write".

  • isolated-write Grok lanes now pass --allow Edit after --allow Bash. Grok's permissions doc groups Write with Edit as one rule name, and live probes show that --allow Edit pre-approves both search_replace and write. The workspace sandbox still refuses writes outside the worktree, and deny rules and hooks still apply. Read-only lanes pass no allow rules, as before.
  • The isolated-write --tools list now includes write. Now that fix(runner): let headless Grok writers run shell commands #81 restores the valid terminal ID, run_terminal_cmd, Grok honors the allowlist. Without this entry, a writer cannot call write at all.
  • provider-dispatch.md names the new rule and says why the lane needs it. It also names the shell commands Grok still cancels under --allow Bash, so writer prompts keep file changes on the file tools.

This PR stacks on #81. Until #81 merges, this PR also shows #81's commits. Review the top three commits, test(runner): reproduce headless Grok writers losing file edits, fix(runner): pre-approve file edits for headless Grok writers, and docs(dispatch): name the shell commands headless Grok still cancels.

Tradeoffs

  • A --tools list with write and no allow rule does not help. That probe still ends with "User cancelled the execution for tool write".
  • --always-approve would approve every tool, which the uses bounded write modes without blanket bypasses test forbids. --no-plan changes nothing.
  • A path-scoped rule such as Edit(<worktree>/**) would not shrink what the lane can write. The pre-approved shell can already write anywhere the sandbox allows, so the sandbox is the real limit.

Verification

  • Bun tests, strict typecheck, static invariants, and plugin validation pass.
  • The exact candidate is installed in every affected harness.
  • The changed behavior passes from each real user surface.
  • The installed version, action, and observed result appear below.

On 7140d30, the two changed tests fail (commands.test.ts: 4 pass, 2 fail). With the fix, bun run test passes 162/162. bun run typecheck, the manifest JSON checks, PSTACK_STATIC_ONLY=1 bash tests/skill-collision-repro.sh, and git diff --check pass.

Live evidence so far comes from the source runner on this branch. A Claude Code session's shell launched it with --parent claude --provider grok --model grok-4.7 --effort xhigh --mode isolated-write on grok CLI 1.0.41, and Grok reported the model as grok-4.7-build. Each lane ran in a fresh scratch git repository. The 7140d30 column is #81's head. It ran once from the installed Claude Code plugin and once from source.

Lane prompt 7140d30 This branch (source)
Create probe.txt with write Grok has no write tool. It either falls back to search_replace, which it then cancels with exit 77, or replies FAILED. No file is created. complete, probe.txt created
Replace alpha with beta in seed.txt with search_replace Exit 77, permission-cancelled on search_replace complete, seed.txt reads beta
Create notes/hello.txt and edit seed.txt, no tool named Exit 77, permission-cancelled on search_replace complete, both changes made
Create a file under $HOME with write No file Grok allows the call, the sandbox returns "Operation not permitted (os error 1)", and no file appears

Grok's session tool_definitions.json for these lanes lists exactly six tools: grep, list_dir, read_file, run_terminal_command, search_replace, and write.

The shell note comes from one-command probes on the same argv, read from Grok's permission_resolved events. Grok allowed touch, mkdir, git add, echo, a single-line and a multi-line python3 -c, a heredoc into cat, and printf ... > /dev/null. It cancelled printf ... > plain.txt, printf ... | tee teed.txt, echo "$(pwd)", echo "${PROBE_UNSET:-x}", and (cd . && ls).

This PR stays a draft until the exact candidate is installed in the Claude Code and Codex harnesses and passes from both.

🤖 Generated with Claude Code

smashru and others added 8 commits September 23, 2026 08:37
Grok isolated-write lanes cancel every run_terminal_command at a
permission prompt the headless CLI cannot show, and the receipt reports
malformed-output with evidence truncated to the init event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grok cancels any tool call that needs an approval prompt when it runs
headless, so isolated-write lanes lost every run_terminal_command and
ended the turn. Writers now pass --allow Bash, which pre-approves the
shell tool while the workspace sandbox, deny rules, and hooks still
apply. The tool list uses Grok's real name, run_terminal_command.

A Grok turn that ends on a permission cancellation now records
permission-cancelled with the cancelled tool result and terminal event
as evidence. Other Grok error results record child-failed. Both keep the
reported model, session, usage, and cost instead of malformed-output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With shell commands pre-approved, a Grok writer can read and send
anything in its environment. The test requires parent tokens and agent
sockets to stay out of it while paths, locale, proxies, and Grok's own
settings pass through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Grok writer's shell is pre-approved, and Grok's workspace sandbox reads
the whole host and, on macOS, never blocks child-process network. The
runner passed it the parent's full environment, so a prompt-injected
writer could send any token there to the network. Grok writers now get an
allowlisted environment: paths, user, shell, locale, terminal, proxy and
CA settings, and GROK_* and XAI_*. Other lanes keep the existing
identity-only scrub.

The dispatch reference now states the real trust boundary instead of
claiming commands outside the workspace are cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grok 1.0.41 accepts run_terminal_cmd as the --tools ID and shows the tool
to the model as run_terminal_command. Passing run_terminal_command makes
Grok drop the whole allowlist, so every lane, read-only included, gets
all 17 tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_terminal_command is the name the model sees, not a valid --tools ID.
Grok drops an allowlist that contains an unknown ID, so every Grok lane
had all 17 tools, including write and search_replace in read-only
lanes. With run_terminal_cmd a writer lane gets exactly its 5 tools and
a reader its 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grok's headless `acceptEdits` mode does not pre-approve its file tools.
An isolated-write lane's `write` and `search_replace` calls stop at an
approval prompt that headless mode cancels, so the lane ends without
changing a file. The writer allowlist also leaves out `write`, Grok's
file-creation tool.

Expect the isolated-write argv to list `write` and to pass `--allow Edit`
after `--allow Bash`. The read-only argv pin is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Headless Grok cancels any tool call that needs an approval prompt. The
`acceptEdits` mode does not pre-approve Grok's file tools, so Grok
cancelled every `write` and `search_replace` call in an isolated-write
lane. The lane exited 77 with `permission-cancelled` and changed nothing.

Pass `--allow Edit` after `--allow Bash` for isolated-write lanes.
Grok's `Edit` rule covers both `search_replace` and `write`, and the
`workspace` sandbox still refuses writes outside the worktree. Add
`write` to the writer's `--tools` allowlist so the lane can call Grok's
file-creation tool. Read-only lanes are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 23, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with the new Grok writer permissions remaining bounded by the workspace sandbox and the failure status fully wired through the runner.

Summary

This PR makes headless Grok writer lanes capable of completing file edits while preserving their workspace sandbox and records provider-reported failures more precisely.

  • Adds the Grok Edit permission rule and write tool for isolated-write lanes.
  • Restricts the environment inherited by pre-approved Grok writer shells.
  • Classifies permission-prompt cancellations separately from malformed output and preserves bounded evidence in receipts.
  • Adds regression coverage and updates provider-dispatch guidance.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Runner receives Grok lane] --> B{Access mode}
  B -->|read-only| C[Plan mode and read-only sandbox]
  B -->|isolated-write| D[Workspace sandbox]
  D --> E[Allow Bash and Edit]
  E --> F[Expose read, terminal, search_replace, and write tools]
  F --> G[Run with filtered environment]
  C --> H[Parse terminal event]
  G --> H
  H -->|Successful result and model proof| I[Write output and complete receipt]
  H -->|Permission prompt cancelled| J[Remove output and write permission-cancelled receipt]
  H -->|Other provider error| K[Remove output and write child-failed receipt]
  H -->|Malformed stream| L[Remove output and write malformed-output receipt]
Loading

Reviews (1) · Last reviewed commit: "fix(runner): pre-approve file edits for ..."

On grok CLI 1.0.41, `--allow Bash` pre-approves plain commands such as
`touch`, `mkdir`, `git add`, and `python3 -c`. Grok still prompts for a
shell command that writes output into a file, such as `> out.txt` or
`| tee out.txt`, even with `--allow Edit`. It also prompts for a command
it cannot split into simple segments, such as `$(...)`,
`${VAR:-default}`, or a subshell. Headless mode cancels each of those
prompts. A heredoc or a multi-line `python3 -c` passes when it does not
redirect into a file.

Tell Grok writers to change files with their file tools and to run build
and test steps as plain commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant