Skip to content

docs: correct grok's read-only story; delete the orphaned grok-args - #788

Merged
aaronjmars merged 1 commit into
mainfrom
docs/grok-readonly-truth
Jul 27, 2026
Merged

docs: correct grok's read-only story; delete the orphaned grok-args#788
aaronjmars merged 1 commit into
mainfrom
docs/grok-readonly-truth

Conversation

@aaronjmars

Copy link
Copy Markdown
Collaborator

Follow-up to #785 and #787. Two loose ends flagged there, which turn out to be the same defect: the docs describe a read-only guard that does not exist, and the code they cite is dead.

The claim, and why every part of it is false

docs/CAPABILITIES.md and docs/harnesses.md both said that a mode: read-only skill is confined on grok by an explicit tool allowlist under grok's own --sandbox read-only, generated by scripts/skill_mode.sh grok-args:

Enforcement is that allowlist plus, for read-only, the sandbox: grok runs headless (grok -p), so any tool that isn't allow-listed [...] is refused.

  • adapters/grok.sh carries no allowlist, deliberately. Headless grok aborts the entire turn on a denied tool (stopReason=Cancelled) instead of degrading, and skills are authored for Claude Code, so they reach for tools no allowlist predicted. It runs --permission-mode bypassPermissions with no --allow and no --deny rules.
  • grok's own --sandbox read-only is silently ignored on grok 0.2.101 — writes still land — and it nest-conflicts with the wrapper sandbox.
  • grok-args had no callers at all once run-grok.sh became setup-only in chore(grok): reduce run-grok.sh to setup-only, move its tests to the adapter #785.
  • The same paragraph also claimed --permission-mode dontAsk, which is not what any code path passes.

An operator reading either doc would have concluded a read-only grok skill was tool-confined. Nothing was confining it. #787 is what actually fixed the runtime hole; this fixes the document that would have told you there was no hole.

What actually enforces read-only

The dispatcher's OS sandbox (harness-adapter/lib/sandbox.shbwrap --ro-bind on Linux, sandbox-exec on macOS), on all six harnesses, plus the workflow's post-run revert. CAPABILITIES.md now lays out the three layers and says which one is load-bearing:

Layer What it does Weight
Tool allowlist Drops Write/Edit/git/gh/python3 from the model's tool set Real narrowing, not a boundary — a shell redirection routes around it, and only the claude and pi adapters consume it
OS sandbox Write-locks the workspace for the whole run The guarantee
Post-run guard Reverts + cleans anything that still landed After-the-fact repair

It also records why native harness sandboxes are not relied on: codex's works but kills the network, grok's is ignored, and pi/vibe/kimi ship none.

grok-args is removed, not rewired

The other option was to point it at the real mechanism. That is not possible — an OS sandbox applied by the dispatcher is not expressible as a permission-flag generator, and the file's entire premise was mirroring allowedTools into grok's grammar. A tombstone comment records why it should not come back in that shape.

Tests

The three grok-args cases asserted the properties of a guard that never worked (grok read-only tier sets --sandbox read-only — grok ignores it). Deleting them outright would leave nothing recording that this was tried and rejected, so they are replaced by a guard that the subcommand stays gone and that the usage line no longer advertises it.

One trap worth noting: the natural way to assert the usage message —

bash "$M" grok-args read-only 2>&1 >/dev/null | grep -q "usage:" && pass ...

fails under the suite's set -o pipefail, because the script's own exit 2 becomes the pipeline's status even though the grep matched. Captured into a variable first.

Also swept up

  • harness-adapter/lib/sandbox.sh — header said "all five harnesses" and named opencode, which is not one of the six.
  • harness-adapter/docs/aeon-integration.md — its three-branch shape (grok -> untouched run-grok.sh) predates refactor(harness): route messages.yml + mcp-server through run-harness #784. Marked superseded rather than rewritten, since it is a historical deployment runbook; it now points a fresh integrator at routing everything through run-harness from the start.

Verification

test_skill_mode (17), test_notify, test_skill_requires, test_harness_adapter_grok, test_run_grok — all pass. okf-validate clean. shellcheck -S warning clean on both edited scripts (sandbox.sh's SC2148 is pre-existing on main — no shebang on a sourced lib — and CI does not run shellcheck).

No runtime behaviour changes: grok-args had no callers, and every other edit is a comment or a document.

The docs described a guard that does not exist. docs/CAPABILITIES.md and
docs/harnesses.md both said a mode: read-only skill is confined on grok by an
explicit tool allowlist under grok's own --sandbox read-only, generated by
scripts/skill_mode.sh grok-args.

Every part of that is wrong:

  * adapters/grok.sh carries no allowlist, deliberately. Headless grok aborts
    the entire turn on a denied tool (stopReason=Cancelled) instead of
    degrading, and skills are authored for Claude Code, so they reach for tools
    no allowlist predicted. It runs --permission-mode bypassPermissions with no
    --allow and no --deny rules.
  * grok's own --sandbox read-only is silently ignored on grok 0.2.101 (writes
    still land) and nest-conflicts with the wrapper sandbox.
  * grok-args itself had no callers once run-grok.sh became setup-only (#785).

So an operator reading either doc would conclude a read-only grok skill was
tool-confined when nothing was confining it. What actually enforces read-only,
on all six harnesses, is the dispatcher's OS sandbox in
harness-adapter/lib/sandbox.sh plus the workflow's post-run revert.

Changes:

  * scripts/skill_mode.sh - grok-args removed, not rewired: the real mechanism
    is not expressible in this file. A tombstone comment records why it should
    not come back in that shape. The header no longer claims allowedTools is
    the enforcement.
  * docs/CAPABILITIES.md - replaces the wrong paragraph with the three
    enforcement layers and which one is load-bearing, plus why native harness
    sandboxes are not relied on (codex's kills the network, grok's is ignored,
    pi/vibe/kimi have none). Also disambiguates capabilities: [read_only]
    (documentation, unenforced) from mode: read-only (enforced).
  * docs/harnesses.md - rewrites the capability-mode section for grok.
  * harness-adapter/lib/sandbox.sh - header said "five harnesses" and named
    opencode, which is not one of the six.
  * harness-adapter/docs/aeon-integration.md - marks its three-branch shape as
    superseded; aeon routes every harness through run-harness now.
  * scripts/tests/test_skill_mode.sh - the three grok-args cases asserted a
    guard that never worked. Replaced with a guard that the subcommand stays
    gone and that the usage line no longer advertises it.

All 17 capability-mode tests pass, plus notify, skill-requires, harness-adapter
grok and run-grok suites; okf-validate clean.
@aaronjmars
aaronjmars merged commit 8a47155 into main Jul 27, 2026
3 checks passed
@aaronjmars
aaronjmars deleted the docs/grok-readonly-truth branch July 27, 2026 18:51
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