Grok Build hooks let you run custom scripts at specific points in the agent’s lifecycle. Before a file is written, after tests run, before a commit is created. They’re the glue between Grok Build’s AI actions and your existing toolchain.
If you’re using Grok Build in CI/CD, hooks are essential. See the headless automation guide for that context. Hooks also pair well with skills and plugins for more complex workflows.
What Are Hooks?
Hooks are scripts that execute at defined lifecycle events. They work like git hooks or npm lifecycle scripts: you define when they run and what they do.
Every action Grok Build takes has a before and after event:
before:file_write → [Grok writes file] → after:file_write
before:shell_command → [Grok runs command] → after:shell_command
before:commit → [Grok creates commit] → after:commit
before:task_complete → [Grok finishes task] → after:task_complete
Hooks can:
- Validate (reject an action if conditions aren’t met)
- Transform (modify the action before it executes)
- Notify (send alerts, update dashboards)
- Chain (trigger additional actions)
Available Hook Events
| Event | Fires when | Can block? |
|---|---|---|
before:file_write | Before any file is created or modified | Yes |
after:file_write | After a file is written | No |
before:file_delete | Before a file is deleted | Yes |
after:file_delete | After a file is deleted | No |
before:shell_command | Before a shell command executes | Yes |
after:shell_command | After a shell command completes | No |
before:commit | Before a git commit is created | Yes |
after:commit | After a git commit is created | No |
before:task_start | Before the agent begins a task | Yes |
after:task_complete | After the agent finishes a task | No |
on:error | When the agent encounters an error | No |
on:subagent_spawn | When a subagent is created | Yes |
Configuration
Hooks are defined in .grok/hooks.json:
{
"hooks": [
{
"event": "after:file_write",
"name": "format-on-save",
"command": "prettier --write {{file}}",
"filePatterns": ["*.ts", "*.tsx", "*.js", "*.jsx"]
},
{
"event": "before:commit",
"name": "lint-check",
"command": "npm run lint",
"blocking": true
},
{
"event": "after:task_complete",
"name": "notify-slack",
"command": "./scripts/notify-slack.sh '{{task_summary}}'",
"blocking": false
}
]
}
Hook Properties
| Property | Type | Description |
|---|---|---|
event | string | The lifecycle event to hook into |
name | string | Human-readable name for logging |
command | string | Shell command to execute |
blocking | boolean | If true, a non-zero exit code stops the action |
filePatterns | string[] | Only trigger for matching files (optional) |
timeout | number | Max seconds before the hook is killed (default: 30) |
env | object | Additional environment variables |
condition | string | Shell expression that must be true to run |
Template Variables
Hooks have access to context variables:
| Variable | Available in | Value |
|---|---|---|
{{file}} | file_write, file_delete | Path to the affected file |
{{files}} | commit | Space-separated list of changed files |
{{command}} | shell_command | The command being executed |
{{task_summary}} | task_complete | Brief description of what was done |
{{exit_code}} | after:shell_command | Exit code of the command |
{{error_message}} | on:error | The error description |
{{agent_id}} | on:subagent_spawn | ID of the new subagent |
Practical Examples
Auto-format after every file write
{
"event": "after:file_write",
"name": "auto-format",
"command": "prettier --write {{file}}",
"filePatterns": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.json"],
"timeout": 10
}
Run type checking before commits
{
"event": "before:commit",
"name": "typecheck",
"command": "npx tsc --noEmit",
"blocking": true,
"timeout": 60
}
If tsc finds errors, the commit is blocked and Grok Build sees the error output. It will attempt to fix the type errors before trying to commit again.
Prevent writes to protected files
{
"event": "before:file_write",
"name": "protect-config",
"command": "echo 'Cannot modify production config' && exit 1",
"filePatterns": ["config/production.*", ".env.production"],
"blocking": true
}
Run tests after any source file changes
{
"event": "after:file_write",
"name": "auto-test",
"command": "npm test -- --related {{file}}",
"filePatterns": ["src/**/*.ts"],
"timeout": 120,
"condition": "test -f package.json"
}
Notify on task completion (CI/CD)
{
"event": "after:task_complete",
"name": "ci-notify",
"command": "curl -X POST $WEBHOOK_URL -d '{\"text\": \"Grok Build completed: {{task_summary}}\"}'",
"env": {
"WEBHOOK_URL": "${SLACK_WEBHOOK}"
}
}
Limit subagent spawning
{
"event": "on:subagent_spawn",
"name": "limit-agents",
"command": "test $(grok status --count-agents) -lt 5",
"blocking": true
}
This prevents more than 5 subagents from running simultaneously.
Security scan on new files
{
"event": "after:file_write",
"name": "security-scan",
"command": "semgrep --config auto {{file}} --quiet",
"filePatterns": ["*.ts", "*.js", "*.py"],
"blocking": false,
"timeout": 30
}
Blocking vs Non-Blocking Hooks
Blocking hooks ("blocking": true):
- If the hook exits with a non-zero code, the action is cancelled
- Grok Build sees the hook’s stderr/stdout and can react to it
- Use for validation, quality gates, and safety checks
Non-blocking hooks ("blocking": false):
- The hook runs but its exit code doesn’t affect the action
- Use for notifications, logging, and side effects
- Failures are logged but don’t interrupt the workflow
Hook Execution Order
When multiple hooks are registered for the same event, they run in the order defined in hooks.json:
{
"hooks": [
{"event": "before:commit", "name": "lint", "command": "npm run lint", "blocking": true},
{"event": "before:commit", "name": "test", "command": "npm test", "blocking": true},
{"event": "before:commit", "name": "typecheck", "command": "npx tsc --noEmit", "blocking": true}
]
}
If any blocking hook fails, subsequent hooks for that event are skipped.
Hooks in Headless Mode
Hooks work identically in headless/CI mode:
grok -p "add input validation to all endpoints" --output-format streaming-json
The hooks fire as normal. Blocking hooks that fail will cause the task to report failure in the streaming output. This is how you enforce quality gates in automated pipelines.
Debugging Hooks
# See which hooks are registered
grok hooks list
# Test a hook without running a full task
grok hooks test "before:commit"
# Run with verbose hook logging
grok --verbose code "make a change"
Hook output appears in Grok Build’s log with the [hook] prefix:
[hook:format-on-save] Running: prettier --write src/auth.ts
[hook:format-on-save] Completed in 0.3s (exit: 0)
Hooks vs Skills
| Feature | Hooks | Skills |
|---|---|---|
| Purpose | React to events | Add capabilities |
| Complexity | Simple (shell commands) | Medium (tools + context) |
| Scope | Per-project | Per-project or global |
| Can add tools? | No | Yes |
| Can block actions? | Yes | No |
| Runs when? | On specific events | When agent decides |
Use hooks for enforcement and automation. Use skills for extending what the agent can do.
FAQ
Can hooks modify the file content before it’s written?
Not directly. before:file_write can block the write but can’t modify the content. If you need to transform content, use an after:file_write hook that reformats the file (like Prettier), and Grok Build will see the formatted version on subsequent reads.
Do hooks run for every subagent action?
Yes. Hooks are global to the project. If a subagent writes a file, after:file_write fires. If you want hooks to only apply to the main agent, check the GROK_AGENT_ID environment variable in your hook script.
What happens if a hook hangs?
Hooks are killed after their timeout value (default 30 seconds). A killed hook is treated as a failure (non-zero exit). For blocking hooks, this means the action is cancelled.
Can I use hooks to inject context into the agent?
Not directly through hooks. Use a CLAUDE.md file or skills for persistent context. However, you can use an after:task_start hook to generate a context file that Grok Build then reads.
Are hooks version-controlled?
Yes. .grok/hooks.json should be committed to your repository so all team members share the same hooks. This is similar to how .husky/ or .githooks/ work.
Can I disable all hooks temporarily?
grok --no-hooks code "quick fix without checks"
This skips all hooks for that session. Useful for debugging or when you know the hooks will fail and want to proceed anyway.