Use Hooks
Run custom scripts at key points in the proto lifecycle — before tool calls, after edits, at session boundaries, and during agent team coordination.
Configure a hook
Hooks are defined in .proto/settings.json (project) or ~/.proto/settings.json (global):
{
"hooks": {
"PreToolUse": [
{
"matcher": "^bash$",
"hooks": [
{
"type": "command",
"command": "./scripts/check-bash-safety.sh",
"timeout": 10000
}
]
}
]
}
}Disable all hooks temporarily without deleting config:
{ "disableAllHooks": true }Hook types
There are three hook types in the system, but only command is configurable via settings.json. The http and prompt types are available through the SDK only.
Settings-configurable
| Type | Purpose |
|---|---|
command | Run a shell script. Event JSON on stdin, decisions on stdout |
SDK-only
| Type | Purpose |
|---|---|
http | POST event JSON to a webhook URL. Available via hookCallbacks. |
prompt | Ask an LLM to make a judgment call. Available via hookCallbacks. |
Command hook
{
"type": "command",
"command": "/path/to/script.sh",
"timeout": 30000,
"env": { "CUSTOM_VAR": "value" }
}Modifiers
async: true — run in the background; output and decisions are ignored.
if — fine-grained argument filter (fires only when the tool’s primary argument matches):
{ "type": "command", "if": "Bash(git *)", "command": "check-git-policy.sh" }Syntax: ToolName(glob). Glob matches command for Bash, file_path for Edit/Write, pattern for Grep.
Events
Lifecycle
| Event | When | Can block? |
|---|---|---|
SessionStart | Session begins or resumes | No |
SessionEnd | Session terminates | No |
PreCompact | Before context compaction | No |
UserPromptSubmit | User submits a prompt | Yes (exit 2) |
Stop | Before model concludes response | Yes (exit 2 or JSON) |
Tool events
| Event | When | Can block? |
|---|---|---|
PreToolUse | Before tool executes | Yes |
PostToolUse | After tool succeeds | Limited |
PostToolUseFailure | After tool fails | Limited |
PermissionRequest | Permission dialog shown | Yes |
Notification | Before a notification is shown | No |
Agent & team events
| Event | When |
|---|---|
SubagentStart | Subagent spawned |
SubagentStop | Subagent finishes |
TeammateIdle | Background agent becomes idle |
TaskCreated | Task added to shared list |
TaskCompleted | Task marked done |
Input/output contract
Exit codes (command hooks)
| Code | Meaning | Behavior |
|---|---|---|
0 | Success | Parse stdout as JSON for decisions |
1 | Non-blocking error | Continue; stderr logged |
2 | Blocking error | Block the action; stderr fed to model |
JSON output
{
"continue": true,
"decision": "allow",
"reason": "explanation"
}Common input fields (all events)
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"hook_event_name": "string",
"timestamp": "ISO 8601"
}Key event-specific fields
PreToolUse — input: tool_name, tool_input. Output: hookSpecificOutput.permissionDecision (allow|deny|ask) and optional permissionDecisionReason.
PostToolUse — input: tool_name, tool_input, tool_response. Output: decision (allow|block).
Stop — input: stop_hook_active, last_assistant_message. Output: decision (allow|block). Check stop_hook_active before continuing to avoid infinite loops.
SessionStart — input: source (startup|resume|clear|compact). Output: hookSpecificOutput.additionalContext injected into session context.
SessionEnd — input: reason. Enum values: clear, logout, prompt_input_exit, bypass_permissions_disabled, other.
PreCompact — input: trigger (manual|auto), custom_instructions. Trigger is manual when fired by /compress, auto when fired by the compaction threshold.
Notification — input: message, title (optional), notification_type. Enum values: permission_prompt, idle_prompt, auth_success, elicitation_dialog.
TeammateIdle — input: agent_id, agent_name, result_summary, success. Exit 2 to send feedback back to the agent.
Matcher patterns
Matchers are regex patterns on tool names (^bash$, read.*) or agent types (^Explore$). Empty string matches all.
For PreCompact, matchers filter on trigger (manual|auto). For Notification, matchers filter on notification_type.
Execution model
- Hooks run in parallel by default. Set
sequential: trueon a hook definition object to force in-order execution. - When multiple hooks conflict, the most restrictive wins:
deny>ask>allow. - Default timeout: 60 seconds. Max output: 1 MB.
- Project hooks require trusted folder status.
SDK hook callbacks
Register hook callbacks directly in TypeScript instead of shell scripts:
import { query, type HookCallback } from '@protolabsai/sdk';
const securityGate: HookCallback = async (input) => {
const data = input as {
tool_name?: string;
tool_input?: Record<string, unknown>;
};
if (data.tool_name === 'Bash') {
const cmd = String(data.tool_input?.command ?? '');
if (cmd.includes('rm -rf')) {
return { shouldSkip: true, message: 'Blocked: destructive command' };
}
}
return {};
};
const conversation = query({
prompt: 'Refactor the auth module',
options: {
hookCallbacks: { PreToolUse: [securityGate] },
},
});Callback return values
| Field | Effect |
|---|---|
shouldSkip | Skip this tool call (PreToolUse only) |
shouldInterrupt | Stop the agent immediately |
suppressOutput | Suppress tool output from conversation |
message | Feedback sent to the agent |
See Contributing → Examples → SDK Hooks for more patterns.
Environment variables
Command hooks inherit process.env plus:
PROTO_PROJECT_DIR — project root
GEMINI_PROJECT_DIR — same (compatibility alias)
CLAUDE_PROJECT_DIR — same (compatibility alias)