Grok Build Source Course · 12 / 19

Hooks: Explicit Deny to Block

Think of Hooks as programmable checkpoints on events. PreToolUse can return an explicit deny; process crashes, timeouts, and unparseable output all go fail-open, letting the tool call proceed.

15 event namesPreToolUse can blockJSON configprocess stdin / stdout
01 / OBJECTIVES

Learning Objectives

Distinguish Two Types of Outcomes

Identify explicit Deny versus Hook execution failure — they produce opposite results for the tool call.

Understand Event Matching

Master the matcher's exact name, regex pattern, and Bash compatibility alias.

Write Testable Configuration

Configure a command Hook following the user guide's JSON structure and design four fault-path tests.

02 / CORE VISUAL

The Decision Path of a Single PreToolUse

03 / EVENTS

The Event Surface in Source Code

Session & Tool

Eight Main-Flow Checkpoints

SessionStart, SessionEnd, Stop, StopFailure, PreToolUse, PostToolUse, PostToolUseFailure, PermissionDenied. Only PreToolUse has is_blocking() returning true.

User, Agent & Compaction

Seven Extended Checkpoints

UserPromptSubmit, Notification, SubagentStart, SubagentStop, compatibility alias SubagentEnd, PreCompact, PostCompact.

Key Boundary

"Event Triggered" ≠ "Controls the Main Flow"

The event enum defines trigger points; is_blocking() separately declares blocking capability. When reading the event list, also trace how results are returned to the caller.

crates/codegen/xai-grok-hooks/src/event.rs
04 / SEMANTICS

Block vs. Fail-open Matrix

Hook Result
Dispatcher Interpretation
Tool Call
JSON decision = deny
Explicit deny
Blocked
No valid JSON, exit code 2
Fallback deny
Blocked
Valid JSON allow, exit code 2
JSON takes priority
Allowed, conflict warning logged
Exit code not 0 or 2
HookRunResult::Failed
Allowed, warning logged
Timeout or process crash
HookRunResult::Failed
Allowed, warning logged
Invalid stdout or unknown decision
Fallback exit code or Failed
Output alone does not block; fallback exit code 2 still denies

Security Implication: Hooks are appropriate for policy advisories, auditing, and recoverable pre-checks. When enforcement guarantees are required, the permission layer and sandbox should also be used. Source code comments explicitly require that Hook failures must not break tool availability.

05 / SOURCE

Real Source Evidence

dispatcher.rs

Failure Defaults to Allow

match result {
    HookRunnerResult::Decision(
        HookDecision::Deny { reason, .. }
    ) => {
        return PreToolUseResult {
            decision: HookDecision::Deny { ... },
            results: run_results,
        };
    }
    HookRunnerResult::Failed(err) => {
        tracing::warn!(
            error = %err,
            "hook failed; ignoring (fail-open)"
        );
    }
    _ => {}
}
crates/codegen/xai-grok-hooks/src/dispatcher.rs
matcher.rs + command.rs

Matching and Exit Codes

pub const DENY_EXIT_CODE: i32 = 2;

pub fn matches(&self, tool_name: &str) -> bool {
    self.regex.is_match(tool_name)
        || self.matches_compat_alias(tool_name)
}

The compatibility mapping lets Bash in configuration hit the internal tool name run_terminal_command. Matchers are compiled from regex; the user guide examples use tool names.

crates/codegen/xai-grok-hooks/src/matcher.rs · runner/command.rs
06 / CONFIG

Configuration Written in the Real JSON Structure

~/.grok/hooks/*.json · project/.grok/hooks/*.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bin/safe-shell-guard.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

The configuration hierarchy is "event → matcher group → handler list". Commands receive the event envelope via stdin; a valid JSON decision takes priority — if no valid JSON is present, the exit code is interpreted, with exit code 2 expressing denial. Global Hooks live in ~/.grok/hooks/; project Hooks live in .grok/hooks/ and are controlled by folder trust. Reserved environment variables are filtered out, and unresolved variables cause an error before startup.

crates/codegen/xai-grok-hooks/examples/hooks/safe-shell.json · xai-grok-pager/docs/user-guide/10-hooks.md
07 / LAB

Lab Exercise: Verify Four Paths

25 MIN

Deliverable
Config, script, test log

  1. Configure a PreToolUse command Hook matching Bash.
  2. Make the script return JSON deny for rm -rf and record the blocked tool result.
  3. Successively trigger exit code 1, timeout, and invalid stdout — verify all three go allowed and produce warnings.
  4. Change the exit code to 2, then verify that invalid stdout can still reach the explicit deny path.
  5. Write a one-sentence boundary statement: which policy must be moved to the permission layer or sandbox.
Takeaway

To judge whether a Hook is safe, ask two questions: can it express an explicit deny, and what does the main flow do when the Hook itself fails? Grok Build's answer is clear — explicit deny blocks, Hook failure goes fail-open.

Source Snapshot Note: This page is based on the hooks crate, user guide, and example configurations from the local grok-build-main snapshot. Code excerpts are for teaching purposes, with log fields and error wrappers omitted; event names, JSON hierarchy, exit codes, and decision semantics match the source.