grok-build-upstream-mirror/crates/codegen/xai-grok-pager/docs/custom-hooks.md
grokkybara[bot] ba76b0a683 Synced from monorepo
Changes:
- Stop hooks for session lifecycle
- Add x.ai/session/state and x.ai/session/import ACP methods
- Deny-and-continue for auto-mode classifier blocks with denial limits
- Drop codebase-upload from dhat soak test
- scheduler_create upsert via task_id; retire one-shot tasks
- Clipboard: copy file fallback + honest toasts for SSH/Apple Terminal
- Polarity-safe syntax colors in minimal mode
- Auto mode classifies unvetted env prefixes instead of hard-prompting
- Add GROK_CLIPBOARD_NO_OSC52 kill switch to force OSC 52 off
2026-07-19 18:42:24 +01:00

11 KiB
Raw Blame History

Custom Hooks Guide

Hooks let you run custom scripts or HTTP requests at key moments during a Grok session — for example, before or after a tool runs, when a session starts or ends, or when the agent sends a notification.

They are perfect for automation, safety checks, logging, notifications, and integrating with your own tools.

Why Use Hooks?

Common use cases:

  • Safety guards: Block dangerous commands like rm -rf / before they execute.
  • Audit logging: Record every tool use or session to a file or external service.
  • Notifications: Send a Slack/Discord message when a long-running task finishes.
  • Auto-formatting: Run cargo fmt or prettier automatically after edits.
  • Environment setup: Export secrets or set variables at session start.
  • Custom workflows: Trigger builds, tests, or deployments on specific events.

Quick Start

  1. Create the hooks directory:

    mkdir -p ~/.grok/hooks
    
  2. Create a simple hook file, e.g. ~/.grok/hooks/session-start.json:

    {
      "hooks": {
        "SessionStart": [
          {
            "hooks": [
             { "type": "command", "command": "echo \"🚀 Grok session started in $(pwd)\"" }
            ]
          }
        ]
      }
    }
    
  3. Start (or restart) a Grok session. The hook runs automatically on SessionStart.

    Try it: press Ctrl+L on nonVS Code family (or run /hooks anywhere — preferred on VS Code / Cursor / Windsurf / Zed) and check the Hooks tab to confirm it's loaded.

Hook Locations

Hooks are discovered from several places (all are merged):

Scope Path Trusted? Notes
Global ~/.grok/hooks/*.json Always Best for personal hooks
Global ~/.claude/settings.json Always Claude Code compatibility
Project <project>/.grok/hooks/*.json Requires trust Per-repo automation
Project <project>/.claude/settings.json Requires trust Claude compatibility
Plugin Bundled inside installed plugins Per-plugin Shared team hooks

Trusting a project: Open the hooks modal (Ctrl+L on nonVS Code family, or /hooks on any terminal including VS Code family) or run /hooks-trust (the same folder-trust gate as --trust, recorded in ~/.grok/trusted_folders.toml) the first time you open a project with hooks. This prevents untrusted repos from running arbitrary code.

The Hook JSON Format

Each .json file can define multiple hooks:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bin/safety-check.sh", "timeout": 10 }
        ]
      }
    ],
    "PostToolUse": [
      {
        "hooks": [
          { "type": "command", "command": "bin/log-activity.sh" }
        ]
      }
    ]
  }
}

Key fields:

  • Event name (top-level key): SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, Notification, SessionEnd, etc.
  • matcher (optional): Regex tested against the event's match value — the tool name on tool events, and per-event values elsewhere (see the user guide's Hooks chapter). Empty = match everything.
  • type: "command" (run a script or shell one-liner) or "http" (POST the event to a URL).
  • command: Path to executable (relative to the JSON file) or inline shell command.
  • timeout: Seconds before killing the hook (default: 5, or 600 for Stop/SubagentStop gates). Hooks fail open on timeout.

Tool name aliases: Claude-style names like Bash, Edit, Read automatically match Grok's internal names (run_terminal_cmd, search_replace, read_file).

Writing Hook Scripts

Input

The full event is sent as JSON on stdin. Example for a PreToolUse hook:

{
  "hookEventName": "pre_tool_use",
  "sessionId": "abc-123",
  "cwd": "/Users/you/project",
  "workspaceRoot": "/Users/you/project",
  "toolName": "run_terminal_cmd",
  "toolInput": { "command": "npm test" },
  "timestamp": "2026-04-14T12:00:00Z"
}

Output (for blocking hooks like PreToolUse)

Write JSON to stdout:

  • Allow: {"decision": "allow"}
  • Deny: {"decision": "deny", "reason": "Unsafe command detected"}

Exit codes (behavior differs by hook type):

  • 0 — success / allow (for blocking hooks)
  • 2 — explicit deny (PreToolUse) or block-stop with stderr as feedback (Stop/SubagentStop; see Stop Decision Control in the user guide)
  • Any other (including timeout/crash/missing env var) — fail-open: the failure is logged and shown in the hook scrollback, but the tool call is not blocked. To block a tool call, return JSON {"decision":"deny","reason":"..."} on stdout.

Passive hooks

For events like SessionStart or PostToolUse, stdout is ignored. Just exit 0 on success.

Useful Environment Variables

Grok injects the following variables into every hook process:

  • GROK_HOOK_EVENT — the event name (e.g. pre_tool_use, session_start, post_tool_use)
  • GROK_HOOK_NAME — the full configured name of this hook
  • GROK_SESSION_ID — the current session identifier
  • GROK_WORKSPACE_ROOT — absolute path to the workspace root

For hooks provided by plugins, the following are also set:

  • GROK_PLUGIN_ROOT — absolute path to the plugin's installation directory
  • GROK_PLUGIN_DATA — absolute path to the plugin's writable data directory

These runner- and plugin-injected variables always take precedence. Attempts to override the reserved runner keys via the env field are stripped at load time (with a warning logged). For plugin hooks, GROK_PLUGIN_ROOT and GROK_PLUGIN_DATA similarly override any user-supplied values for those keys.

Custom Environment Variables (env field)

Each handler can declare additional env vars to inject into the child process:

{
  "type": "command",
  "command": "bin/check.sh",
  "env": {
    "MY_API_TOKEN": "secret-here",
    "LOG_LEVEL": "debug"
  }
}

Values must be strings — JSON numbers and bools currently fail to parse (wrap them in quotes if you need them).

For plugin hooks, the plugin adapter additionally injects GROK_PLUGIN_ROOT and GROK_PLUGIN_DATA. These keys override any user-declared values for the same names (the plugin contract is non-negotiable).

Variable Substitution

command and url strings support $VAR and ${VAR} substitution at config-load time:

{
  "type": "command",
  "command": "${HOME}/.config/grok-hooks/check.sh"
}

Lookup order for each reference:

  1. The handler's own env map.
  2. The current process environment (the env Grok itself sees).

If a reference is unset in both, it's preserved verbatim (e.g. ${UNSET} stays as the literal string). The runtime sh -c branch may resolve it later if the var becomes set; otherwise the runner refuses to spawn with a clear "required env var(s) not set" error.

For HTTP hooks specifically, url is also re-expanded at request time (immediately before SSRF validation), so plugin-injected vars like ${GROK_PLUGIN_ROOT}/check resolve against the plugin's actual path.

Parameter-expansion modifiers

POSIX parameter-expansion forms — ${VAR:-default}, ${VAR-default}, ${VAR:=x}, ${VAR:?msg}, ${VAR:+x}, ${VAR%pat}, ${VAR#pat}, ${VAR/pat/repl}, ${VAR:N:M} — are never expanded at load time and are left verbatim for the runtime sh -c branch to handle. This avoids subtle divergences between the load-time expander and POSIX shell semantics (notably, the empty-string behaviour of :-).

If your hook command contains shell metacharacters (spaces, pipes, &&, redirects, $, etc.), the runner routes it through sh -c and you get full shell-expansion semantics. If your command is a bare path with no metachars, the runner spawns it directly — but $VAR / ${VAR} references in the path are still resolved at load time so direct-exec paths like ${HOME}/bin/check.sh work without needing to be wrapped in sh -c.

What is NOT expanded

  • matcher is a regex ($ is the regex anchor for end-of-line). It is never env-expanded — substituting $VAR would silently change the regex's semantics and likely produce an invalid pattern. If you need a dynamic matcher, generate the JSON file at write time.
  • timeout is numeric, so there is nothing to expand.
  • The values of the env map itself — these are stored verbatim and passed to the child as-is, so "BAR": "${HOME}/x" injects the literal string ${HOME}/x into the child's environment.

Managing Hooks in the TUI

Press Ctrl+L on nonVS Code family (or run /hooks anywhere) to open the Hooks & Plugins modal.

In the Hooks tab you can:

  • l — Reload all hooks
  • a — Add a custom hook by path (great for testing)
  • e — Enable/disable
  • r — Remove
  • Space — Expand groups

Hooks from ~/.grok/hooks/ appear under Global, project ones under Project, etc.

HTTP Hooks

Instead of a local script, call a remote endpoint:

{ "type": "http", "url": "https://hooks.example.com/grok-event", "timeout": 15 }

The full event envelope is POSTed as JSON. Useful for webhooks, analytics, or serverless functions.

Best Practices

  1. Keep hooks fast — long-running hooks block the UI (use background & or async where possible).
  2. Use explicit deny to block — hooks fail-open on any error (timeout, crash, missing env var, etc.), so a hook that crashes will not block the tool call. To enforce policy, your hook must run to completion and emit {"decision":"deny","reason":"..."} on stdout.
  3. Use absolute paths or relative to hook file — scripts in bin/ next to the JSON are portable.
  4. Test with Ctrl+L (nonVS Code family) / /hooks — verify loading and matching before relying on them.
  5. Version control project hooks — commit .grok/hooks/ (but never secrets).

Security Notes

  • Global hooks (~/.grok/...) run with your user permissions — treat them like shell scripts.
  • Project hooks require explicit trust (run /hooks-trust or use the modal) to prevent supply-chain attacks from malicious repos.
  • HTTP hooks send session data — only use trusted endpoints.

Troubleshooting

  • Hook not running? → Press Ctrl+L on nonVS Code family (or run /hooks anywhere) to see if it's loaded and matched.
  • Project hooks ignored? → Trust the project first.
  • Script not found? → Check the path is relative to the .json file and executable (chmod +x).
  • See errors? → Check the pager logs (usually in the tracing pane or ~/.grok/logs).

More Examples

See the built-in examples in the xai-grok-hooks crate:

Copy them to ~/.grok/hooks/ and customize.

Full Reference

For the complete event list, matcher semantics, trust model, and advanced details, see the Hooks user guide.


Happy hooking! If you build something cool, consider sharing it as a plugin.