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:
```sh
mkdir -p ~/.grok/hooks
```
2. Create a simple hook file, e.g. `~/.grok/hooks/session-start.json`:
```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 non–VS 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):
**Trusting a project**: Open the hooks modal (`Ctrl+L` on non–VS 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.
- **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.
- 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:
```json
{
"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:
```json
{
"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}`,
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` (non–VS 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 non–VS 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: