The Grok Build control model, explained
Grok Build is xAI’s terminal coding agent — beta in May 2026, 1.0 on August 7, and open-sourced in mid-July as a ~1M-line Rust workspace (Apache 2.0, one-way mirror of the internal monorepo). That open-sourcing matters for this page: the control model below isn’t inferred from marketing copy, it’s read out of the official docs and the shipped hook-runner source. This page is the reference: what Grok Build ships natively for controlling tool execution, exactly how each mechanism behaves, and where the model ends.
This page covers Grok Build’s own controls. For governing Grok Build with ACP, see the install guide; for the cross-harness picture, the controls comparison.
Permission modes: five postures, two commonly confused
[ui] permission_mode (or the matching flags) selects the posture:
| Mode | What it does |
|---|---|
default |
Side-effectful calls prompt a human. |
auto |
A built-in classifier auto-approves calls it judges safe; dangerous ones may still prompt. |
plan |
Everything holds behind plan review — the plan gate is never skipped. |
acceptEdits |
File edits auto-approve; shell still prompts. |
| always-approve | --always-approve / --yolo / Ctrl+O: every permission check answers yes. |
dontAsk |
Headless: anything without an explicit allow rule is silently denied. |
The two worth separating, because early coverage (ours included) conflated them: always-approve is the escape hatch; dontAsk is the opposite of one. dontAsk exists for headless scripting and fails closed — no allow rule, no execution, no prompt. That’s the strictest default posture in the whole comparison table, and it’s hiding under a name that sounds permissive. If you run Grok Build in CI, dontAsk plus a tight allow list is a genuinely defensible setup — with the caveat that the denials are silent, which brings us to the audit section.
Rules: TOML, deny-wins
Rules live in config.toml (user-global) or arrive per-invocation:
[permission]
rules = [
{ action = "allow", tool = "bash", pattern = "git status*" },
{ action = "deny", tool = "bash", pattern = "git push*" },
]
--allow / --deny flags take the same tool/pattern form. Precedence is clean: deny always wins; an explicit allow auto-approves even calls the classifier considers dangerous; a remembered “always allow” (from the prompt’s remember option) still re-prompts on dangerous patterns like rm and git push. Tool filters cover the built-ins — Bash, Edit, Read, Grep, MCPTool, WebFetch, WebSearch.
Two things the rules are not: they are not Claude Code’s permissions.allow syntax (despite the settings compatibility below — see the next section), and they are not project-settable in a way a repository can smuggle in. Deny rules and PreToolUse hooks apply in every mode, including always-approve — the single most control-friendly fact about this harness.
The hook system: broad surface, one blocking event, fail-open core
Grok Build’s hook system is the most Claude-compatible we’ve verified after Muse Code’s, and the compatibility is deliberate — but partial in ways that matter.
Where hooks load from. ~/.grok/hooks/*.json (always trusted), project .grok/hooks/*.json (behind folder trust, same gate as project MCP servers), TOML blocks in config.toml — and, on by default, hooks found in your real ~/.claude/settings.json, settings.local.json, and ~/.cursor/hooks.json. Matchers alias Claude tool names (Bash matches run_terminal_command), and CLAUDE_PROJECT_DIR is injected as a compat alias. If you have Claude Code hooks installed today, Grok Build is already running them.
The event surface is wide — SessionStart/End, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, PermissionDenied, Stop/SubagentStop and friends, compaction events — but only PreToolUse can block a tool call. UserPromptSubmit is explicitly observe-only (exit code and stdout ignored — unlike Claude Code). Stop hooks gate with Claude’s block/approve vocabulary and a 600-second budget.
The output contract is where Claude compatibility ends. The hook runner parses a top-level decision field: "deny" (plus reason) blocks, "allow" or absent allows, and any other value is an unknown-decision error. It does not parse Claude’s hookSpecificOutput.permissionDecision — xAI’s own example emits that key “for forward-compatibility” only — and Claude’s legacy "approve"/"block" values are unknown-decision errors. There is no ask. Exit code 2 is an alternate deny channel. The practical consequence: a Claude Code hook picked up through the compat path fires, runs, and its verdict is ignored — it parses as decision-absent, which is an allow. The hook looks installed; the control isn’t there.
And the failure posture is fail-open, everywhere. Default timeout 5 seconds; a timed-out, crashed, or malformed hook logs to the scrollback and the call proceeds. Only an explicit deny blocks. That’s a defensible choice for a harness (a broken chime shouldn’t brick your session), but it means a hook-based control layer inherits “proceed” as its failure mode unless it brings its own posture. Compare Muse Code, whose staged review holds what it can’t parse — the two harnesses made opposite calls, and Grok’s is the one you have to design around.
One genuinely distinctive capability rides the same contract: hookSpecificOutput.updatedInput lets a PreToolUse hook rewrite the tool input before execution (a schema-invalid rewrite blocks instead). And type: "http" hooks POST the full event envelope to a URL — a control plane can stand on the call path with no local shim at all.
Settings compatibility: hooks yes, permissions no
The [compat.claude] cells cover skills, rules, agents, MCP servers, and hooks. There is no permissions cell: permissions.allow/permissions.deny in .claude/settings.json are not read. If your control posture lives in Claude settings permission rules, none of it transfers — only the hooks do, and (per above) their decisions don’t parse. Enterprise MDM managed-settings.json is honored for a subset, but Claude’s disableBypassPermissionsMode is deliberately not applied to Grok’s always-approve; locking that down needs Grok’s own root-owned [ui] disable_bypass_permissions_mode = true.
Sandbox
Five profiles — off (default), workspace, devbox, read-only, strict — enforced by Landlock (Linux) and Seatbelt (macOS), with custom sandbox.toml profiles and per-path deny globs. Two documented caveats: the sandbox is off by default, and child-process network blocking is Linux-only — a documented no-op on macOS. --sandbox read-only composes well with dontAsk for a genuinely locked-down headless run.
Audit
A session log exists (~/.grok/sessions for headless runs), and the source records PermissionDecision events internally — but the on-disk format is undocumented and there’s no query surface. dontAsk’s silent denials and auto’s classifier approvals both land in the same undocumented store. As with every classifier this season, the decision loop got a model; the decision record didn’t get an interface.
Context worth knowing when weighing the audit story: the July session-trace exfiltration incident (multi-gigabyte trace uploads, including unread files, followed by a retention-policy change and the open-sourcing) is precisely the class of event an independent, queryable decision ledger exists to catch.
Escape hatches and the empty chair
The hatch is --always-approve / --yolo (permissionMode: "bypassPermissions" in hook payloads). Even there, deny rules and PreToolUse hooks still fire — so a deny-capable hook holds through yolo, which is more than most harnesses give you. The empty-chair question — what happens to a “should I?” when nobody’s answering — resolves differently per mode: default/plan put a human in the chair; auto seats a classifier; always-approve answers yes on your behalf; dontAsk answers no, silently. Grok Build is the rare harness where the honest answer to the empty chair is “configurable in both directions” — the failure modes are the silent ones: hook failures allow quietly, and dontAsk denies quietly. In both cases something decided, and nothing legible recorded why.