# Grok Build Hooks Reference — PreToolUse in Every Mode, Fail-Open by Design

How Grok Build's PreToolUse hook works: fires in every permission mode, fails open on timeout or crash, its decision contract, and why --local isn't wired yet.

<div style="background:var(--acp-surface,#f8f9fb);border:1px solid var(--acp-border,#e2e5ea);border-radius:12px;padding:20px 24px;margin:24px 0;">
<p style="margin:0 0 10px;font-weight:600;">Just want the answer?</p>
<p style="margin:0 0 12px;">Grok Build's <code>PreToolUse</code> hook fires in every permission mode, <code>--always-approve</code> included, and reads deny rules from your <code>~/.grok/hooks/acp.json</code>. Install it with:</p>
<pre style="margin:0 0 12px;overflow-x:auto;"><code>curl -sf https://agenticcontrolplane.com/install.sh | bash</code></pre>
<p style="margin:0;">Full walkthrough: <a href="/integrations/grok-build">Grok Build install guide</a>. Native control model: <a href="/controls/grok-build">Grok Build permissions, explained</a>.</p>
</div>

Grok Build is xAI's terminal coding agent, and its hook system is the most Claude-compatible of the harnesses in this reference series — it reads your real Claude Code settings files, aliases Claude's tool names, and even accepts Claude's JSON output shape "for forward-compatibility." None of that compatibility extends to holding by default: Grok Build's own hook runner is fail-open, and the field it actually parses for a decision isn't the one most Claude-lineage hooks write. This is the reference for what fires, what it returns, and what a control hook has to bring on its own to hold in every mode Grok Build ships.

## Where hooks live

Grok Build reads hook registrations from three places, in ascending order of trust required:

- `~/.grok/hooks/*.json` — always trusted, no prompt.
- `<project>/.grok/hooks/*.json` — only after the folder is trusted once.
- Inline `[[hooks]]` blocks in `config.toml`.

And, on by default: Grok Build also discovers hooks in your existing `~/.claude/settings.json`, `~/.claude/settings.local.json`, and `~/.cursor/hooks.json`. If you already run Claude Code or Cursor hooks, Grok Build is already invoking them — a real compatibility surface, not a marketing line.

## Five permission modes, and where PreToolUse sits

| Mode | What it means |
|---|---|
| `default` | Prompts for anything not pre-approved |
| `plan` | Read-only; no edits or shell commands run |
| `acceptEdits` | File edits auto-approved; shell commands still prompt |
| `always-approve` (`--yolo`) | Every native prompt answered yes automatically |
| `dontAsk` (headless) | No prompts at all — unmatched calls are silently denied |

The two worth not confusing: `always-approve` is the escape hatch that answers *yes* to a human-facing prompt, and `dontAsk` is the strict headless posture that denies anything a rule doesn't explicitly cover. Both matter for hooks for the same reason: **deny rules and PreToolUse hooks fire in every mode, `always-approve` included.** The flag removes the confirmation dialog; it does not remove the hook.

## The event surface — only one event blocks

Grok Build fires `SessionStart`, `SessionEnd`, `UserPromptSubmit` (observe-only — unlike Claude Code, it cannot deny here), `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`, `Stop`, `SubagentStop`, and a compaction event. Of all of these, only `PreToolUse` can block a call before it runs.

## The output contract: a top-level field, not `hookSpecificOutput`

A Grok Build hook script writes JSON to stdout. The field the runner actually parses is a top-level `decision`:

```json
{ "decision": "deny", "reason": "Blocked by policy" }
```

`"allow"` or an absent field lets the call proceed; exit code `2` is an alternate deny path read from stderr. Here's the trap: Claude Code hooks write their verdict under `hookSpecificOutput.permissionDecision`, and Grok Build's docs describe accepting that shape too — "for forward-compatibility." In practice, a hook that only sets `hookSpecificOutput.permissionDecision: "deny"` and leaves the top-level `decision` field unset parses as **decision-absent, which is allow**. A hook can run, correctly compute a deny, and still let the call through because it wrote the verdict in the wrong place. Any control hook targeting Grok Build has to write the flat `decision` field to actually block anything.

## Fail-open by design — and why a control hook can't inherit it

Grok Build's default hook timeout is 5 seconds. A timeout, a crash, or malformed stdout all resolve to allow — only an explicit `"decision": "deny"` stops a call. That's a defensible choice for the harness itself: a broken chime shouldn't brick your coding session. It also means anything built on the same hook contract inherits "proceed" as its failure mode unless it brings its own posture on top.

The ACP hook for Grok Build answers this directly, and it doesn't fail open uniformly: in interactive/auto sessions it fails open loud (a `[ACP] ⚠ UNGOVERNED` warning to stderr plus a durable line in `~/.acp/lapse.log`), while unattended tiers (headless, CI, `dontAsk`) fail closed — nobody's watching the scrollback to notice a silent gap, so the safer failure there is to stop.

## The missing "ask," and how it resolves

Grok Build's own hook contract has no `ask` outcome — only allow or deny. The ACP hook resolves this by mode: in `default`/`plan`, an `ask` verdict defers to Grok Build's own native confirmation prompt, which still stands after the hook returns; in `auto`/`always-approve`/headless modes, where there's no prompt to defer to, `ask` becomes a deny with a link to resolve it in the console. An explicit local allow rule outranks an `ask` in attended modes; a `deny` always holds regardless of mode.

## Installing it: `~/.grok/hooks/acp.json`

```bash
curl -sf https://agenticcontrolplane.com/install.sh | bash
```

Manually:

```bash
mkdir -p ~/.acp/hooks/grok-build ~/.grok/hooks
curl -fsSL https://agenticcontrolplane.com/hooks/grok-build/hook.mjs \
  -o ~/.acp/hooks/grok-build/hook.mjs
curl -fsSL https://agenticcontrolplane.com/hooks/grok-build/acp.json \
  -o ~/.grok/hooks/acp.json
```

The registration file points Grok Build at the local hook script; the hook script itself calls the gateway, not the other way around, so rotating your ACP key is a credential-file overwrite, not a reinstall.

## `--local` isn't wired for Grok Build, and why

Run the installer's own source and you'll find Grok Build explicitly excluded under `--local`: the step is skipped with a message that its hook needs a workspace to check a call against, and local decisions aren't wired there yet. The reasoning: Claude Code's and Cursor's hooks call an on-device policy engine (`~/.acp/policy.json` plus a local decide script) that `--local` mode seeds for exactly this purpose. Grok Build's hook script has no equivalent local path yet — it always calls the hosted `/govern/tool-use` endpoint, so running it without a workspace key means it has nothing to check against. The installer skips the integration outright rather than register a hook that would always no-op. The same skip applies to Muse Code, Antigravity, dsh, pi, Prime Agent, and opencode. OpenClaw is the one exception among the harnesses in this series — see its [own hooks reference](/blog/openclaw-hooks-reference) for why.

## Known limitations

- Hooks see tool calls, not tokens: model spend isn't metered for Grok Build today — no proxy or transcript path ships for it, so tool-call rows (allow/deny, with the native tool name preserved) are what's recorded, not per-call token cost.
- A hook can only judge what it's shown at the moment it's called — the same limit as every hook-based control layer, Grok Build's included.
- Grok Build's own headless session log (`~/.grok/sessions`) has no documented query surface of its own; the ACP activity log is the queryable record of what the hook decided.

## Comparing to Claude Code hooks

The shapes are close but not identical. Claude Code's hook contract parses `hookSpecificOutput.permissionDecision` and has a real `ask` outcome baked in; Grok Build parses a flatter top-level `decision` and only has allow/deny. Both fire in their respective "auto-approve everything" modes — Claude Code's `bypassPermissions` and Grok Build's `--always-approve` — which is exactly why both need a hook that fails closed for unattended use rather than trusting the harness's own default. See the [Claude Code hooks reference](/blog/claude-code-hooks-reference) for the comparison in the other direction.

## Frequently asked questions



## Where to read more

- [Grok Build permissions, explained](/controls/grok-build)
- [Grok Build install guide](/integrations/grok-build)
- [Claude Code hooks reference](/blog/claude-code-hooks-reference)
- [Codex hooks reference](/blog/codex-cli-hooks-reference)
- [grok-build-acp-plugin source](https://github.com/agentic-control-plane/grok-build-acp-plugin)
- <!-- source: https://docs.x.ai/build/features/hooks --> [Grok Build hooks documentation](https://docs.x.ai/build/features/hooks)
