Lifecycle hooks
Run shell commands automatically at key points in a Bob session to log activity, inject context, or block actions based on your own logic.
Lifecycle hooks let you run shell commands at specific points in a Bob session. Use them to log activity, inject context into the model, gate or block actions, or start follow-up automation, all without modifying Bob itself.
Supported hooks
| Hook | When it runs | Blocking | Stdout behaviour |
|---|---|---|---|
SessionStart | Once when a session begins | No | Injected as context |
UserPromptSubmit | Each time you submit a prompt | Yes (exit 2) | Injected as context |
PreToolUse | Before a matched tool runs | Yes (exit 2) | Ignored |
PostToolUse | After a matched tool completes | No | Ignored |
Stop | When the agent stops | No | Ignored |
Configuration
Hooks are defined under the hooks key in your settings.json. Bob merges hooks from two locations:
| Scope | File |
|---|---|
| Global (all workspaces) | ~/.bob/settings/settings.json |
| Workspace (current project) | .bob/settings.json |
Global hooks always run. Workspace hooks are merged on top of global hooks and apply only to the current project.
Hook schema
{
"hooks": {
"PreToolUse": [
{
"matcher": "^write_file$",
"hooks": [
{
"type": "command",
"command": "sh .bob/hooks/check.sh",
"timeout": 5
}
]
}
]
}
}Configuration fields
| Field | Type | Default | Description |
|---|---|---|---|
type | "command" | (none) | Required. Only command is supported. |
command | string | (none) | Required. The shell command to run. Runs via sh -c on macOS/Linux, cmd /c on Windows. |
matcher | string | (none) | Optional. A regex matched against the tool name (PreToolUse, PostToolUse only). Omit to match all tools. |
timeout | number | 10 | Seconds before the hook is stopped. Set to 0 to disable the timeout. |
Hook reference
Runs once when a new session begins, before the first turn.
Stdin schema
{
"event": "string",
"session_id": "string"
}Example payload
{
"event": "SessionStart",
"session_id": "ses_01abc123"
}Stdout: Written to the model's context as additional session information.
Blocking: Exit code 2 is not supported. The session always starts. Other non-zero exits are logged and ignored.
Runs each time you submit a prompt, before it is sent to the model.
Stdin schema
{
"event": "string",
"session_id": "string",
"prompt": "string"
}Example payload
{
"event": "UserPromptSubmit",
"session_id": "ses_01abc123",
"prompt": "Refactor the auth module"
}Stdout: Written to the model's context alongside the prompt.
Blocking: Exit code 2 blocks the prompt from being sent. Bob shows an error and the prompt is not submitted.
Runs before a matched tool runs, giving you a chance to inspect or block the action.
Stdin schema
{
"event": "string",
"session_id": "string",
"tool": "string",
"input": "object"
}Example payload
{
"event": "PreToolUse",
"session_id": "ses_01abc123",
"tool": "write_file",
"input": {
"path": "src/index.ts",
"content": "..."
}
}Stdout: Ignored.
Blocking: Exit code 2 prevents the tool from running. Bob reports the tool as blocked and continues the session.
Runs after a matched tool completes, regardless of whether it succeeded.
Stdin schema
{
"event": "string",
"session_id": "string",
"tool": "string",
"input": "object",
"output": "string"
}Example payload
{
"event": "PostToolUse",
"session_id": "ses_01abc123",
"tool": "write_file",
"input": {
"path": "src/index.ts",
"content": "..."
},
"output": "File written successfully"
}Stdout: Ignored.
Blocking: Exit code 2 has no effect. The tool has already run.
Runs when the agent stops, after the final turn completes.
Stdin schema
{
"event": "string",
"session_id": "string"
}Example payload
{
"event": "Stop",
"session_id": "ses_01abc123"
}Stdout: Ignored.
Blocking: Exit code 2 has no effect. The session has already ended.
Exit codes and blocking
| Exit code | Behaviour | Applies to |
|---|---|---|
0 | Success: hook ran without issue | All hooks |
2 | Block: stop the current action | UserPromptSubmit, PreToolUse |
| Any other non-zero | Non-blocking failure: logged and ignored | All hooks |
Only UserPromptSubmit and PreToolUse support blocking. Exit code 2 from SessionStart, PostToolUse, or Stop is treated as a non-blocking failure.
Command details
- Working directory: Commands run from the task working directory (the folder Bob is working in).
- Default timeout: 10 seconds. Override per-hook with the
timeoutfield. Settimeoutto0to disable the timeout entirely. - Stderr: Written to Bob's logs but does not affect the hook result.
- Shell: Commands run via
sh -con macOS and Linux, andcmd /con Windows.
Getting started
Open or create your global settings file at ~/.bob/settings/settings.json.
Add a hooks key with the hook you want to use. The example below runs a script before every write_file call:
{
"hooks": {
"PreToolUse": [
{
"matcher": "^write_file$",
"hooks": [
{
"type": "command",
"command": "sh ~/.bob/hooks/log-write.sh"
}
]
}
]
}
}Create the script file. This minimal script logs the incoming JSON payload:
#!/bin/sh
# ~/.bob/hooks/log-write.sh
cat >> ~/.bob/hooks/write-log.txtStart a Bob session and use the matched tool. Check ~/.bob/hooks/write-log.txt to confirm the hook ran and the payload was written.
Examples
Log all hook input
Write every hook's stdin to a file for debugging:
#!/bin/sh
# Append the incoming JSON payload with a timestamp
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> ~/.bob/hooks/debug.log
cat >> ~/.bob/hooks/debug.log
echo "" >> ~/.bob/hooks/debug.logConfigure this under any hook:
{
"hooks": {
"SessionStart": [
{
"hooks": [{ "type": "command", "command": "sh ~/.bob/hooks/debug.sh" }]
}
]
}
}Inject session context
Return text from a SessionStart hook to add it to the model's context:
#!/bin/sh
# Output project metadata for the model to use
echo "Project: $(basename $PWD)"
echo "Git branch: $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')"
echo "Node version: $(node --version 2>/dev/null || echo 'not installed')"Block a prompt
Exit with code 2 from a UserPromptSubmit hook to prevent a prompt from being sent:
#!/bin/sh
# Block prompts containing the word "delete"
PROMPT=$(cat | python3 -c "import sys,json; print(json.load(sys.stdin)['prompt'])")
case "$PROMPT" in
*delete*|*DELETE*)
echo "Prompt blocked: contains 'delete'" >&2
exit 2
;;
esacBlock a matched tool
Exit with code 2 from a PreToolUse hook to prevent a specific tool from running:
#!/bin/sh
# Block write_file operations on files outside the src/ directory
PATH_VAL=$(cat | python3 -c "import sys,json; print(json.load(sys.stdin)['input'].get('path',''))")
case "$PATH_VAL" in
src/*) ;;
*)
echo "Blocked: writes outside src/ are not allowed" >&2
exit 2
;;
esacRun follow-up automation from Stop
Use Stop to start cleanup or reporting after a session ends:
#!/bin/sh
# Commit any staged changes after the agent finishes
cd "$PWD"
git diff --cached --quiet || git commit -m "chore: auto-commit from Bob session"Current limitations
Only command hooks and the five hook types listed above are supported in this release. The following are not yet available:
- Hook types other than
command: function hooks, inline script hooks, and similar are not supported. - Scheduled hooks: hooks cannot be set to run on a timer or in response to an external event.
- Input rewriting: hooks cannot modify the prompt or tool input before it reaches the model.
- Sandboxed running: hooks run with your full user permissions; no isolation is applied.
- Dedicated hook telemetry: hook activity is not tracked separately in session analytics.
- Blocking from
PostToolUseorStop: exit code2has no effect for these hooks.
Custom rules
Custom rules influence how Bob responds to your requests, aligning output with your specific preferences and project requirements. Configure custom rules to control Bob's coding style, documentation approach, and decision-making processes.
Telemetry data
Learn about the telemetry data IBM Bob collects, how it's used to improve the product, and how to enable or disable data collection.