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.
Managing hooks in Bob Settings
The Hooks tab in Bob Settings lists all configured global and workspace hooks. For each hook, the list shows the event type, matcher, scope, status, and command. You can search, filter, sort, and paginate the list, toggle individual hooks on or off, and open the settings file where a hook is defined.
To enforce hooks across all users in your organization, see EnforcedHooks.
Supported hooks
| Hook | When it runs | Blocking | Stdout behavior |
|---|---|---|---|
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 or JSON deny) | Optional JSON to deny the call or replace its input |
PostToolUse | After a matched tool succeeds | No | Appended to the tool result |
PreCompact | Before context compaction begins | Yes (exit 2) | Ignored |
PostCompact | After context compaction 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" | "http" | (none) | Required. The handler type. Use command to run a local shell command or http to send events to an HTTPS endpoint. |
command | string | (none) | Required when type is command. The shell command to run. Runs via sh -c on macOS/Linux, cmd /c on Windows. |
url | string | (none) | Required when type is http. The HTTPS URL to send hook event payloads to. Only https:// URLs are accepted. |
matcher | string | (none) | Optional. A regex matched against the tool name for PreToolUse and PostToolUse, the source for SessionStart, and the trigger for PreCompact and PostCompact. Other events ignore it. Omit or use * to match everything. |
timeout | number | 10 | Seconds before the hook is stopped. Must be greater than 0. |
Hook reference
Runs once when a new session begins, before the first turn.
Stdin schema
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"source": "startup | resume | compact"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "SessionStart",
"source": "startup"
}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
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"prompt": "string"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "UserPromptSubmit",
"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
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"tool_name": "string",
"tool_input": "object",
"tool_use_id": "string"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "PreToolUse",
"tool_name": "write_file",
"tool_input": {
"path": "src/index.ts",
"content": "..."
},
"tool_use_id": "toolu_01abc123"
}Stdout: Optional JSON, read when the hook exits 0. Set hookSpecificOutput.hookEventName to "PreToolUse"; otherwise Bob ignores the output. Plain-text stdout is ignored.
To block the tool, set permissionDecision to "deny". Bob sends permissionDecisionReason to the model as the reason:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Writes to the config directory are not allowed"
}
}To replace the tool input before it runs, omit permissionDecision (or set it to "allow") and provide updatedInput. The new input replaces the original entirely, and the normal tool approval still applies:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { "command": "git status --short" }
}
}Any other permissionDecision value causes Bob to ignore the output.
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 successfully. It does not run when the tool returns an error.
Stdin schema
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"tool_name": "string",
"tool_input": "object",
"tool_response": "string",
"tool_use_id": "string"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "PostToolUse",
"tool_name": "write_file",
"tool_input": {
"path": "src/index.ts",
"content": "..."
},
"tool_response": "File written successfully",
"tool_use_id": "toolu_01abc123"
}Stdout: Appended to the tool result that the model sees. You can also print JSON with hookSpecificOutput.hookEventName set to "PostToolUse" and an additionalContext string.
Blocking: Exit code 2 has no effect. The tool has already run.
Runs before context compaction begins, giving you a chance to inspect conditions or block compaction.
Stdin schema
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"trigger": "manual | auto",
"custom_instructions": "string | null"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "PreCompact",
"trigger": "auto",
"custom_instructions": null
}Stdout: Ignored.
Blocking: Exit code 2 blocks compaction. Bob shows a "Compaction blocked by hook" error and the current turn ends.
Runs after context compaction completes.
Stdin schema
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"trigger": "manual | auto",
"compact_summary": "string"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "PostCompact",
"trigger": "auto",
"compact_summary": "..."
}Stdout: Ignored.
Blocking: Exit code 2 has no effect. Compaction has already completed.
Runs when the agent stops, after the final turn completes.
Stdin schema
{
"session_id": "string",
"cwd": "string",
"hook_event_name": "string",
"last_assistant_message": "string | null"
}Example payload
{
"session_id": "3f2a9c1e8b7d4f6a0e5c2b1d9a8f7e6c",
"cwd": "/path/to/project",
"hook_event_name": "Stop",
"last_assistant_message": "Done. The auth module is refactored."
}Stdout: Ignored.
Blocking: Exit code 2 has no effect. The session has already ended.
Exit codes and blocking
| Exit code | Behavior | Applies to |
|---|---|---|
0 | Success: hook ran without issue | All hooks |
2 | Block: stop the current action | UserPromptSubmit, PreToolUse, PreCompact |
| Any other non-zero | Non-blocking failure: logged and ignored | All hooks |
Only UserPromptSubmit, PreToolUse, and PreCompact support blocking. Exit code 2 from SessionStart, PostToolUse, PostCompact, 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. The value must be greater than0. - Stderr: Written to Bob's logs. When a hook blocks with exit code
2, stderr (or stdout if stderr is empty) is used as the block reason. - 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)['tool_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
The following capabilities are not yet available:
- Hook handler types other than
commandandhttp: 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.
- Prompt rewriting: hooks cannot change the submitted prompt;
UserPromptSubmitcan only add context or block. - 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 non-blocking hooks: exit code
2has no effect forSessionStart,PostToolUse,PostCompact, orStop.
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.