Skip to content

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):

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^bash$",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/check-bash-safety.sh",
            "timeout": 10000
          }
        ]
      }
    ]
  }
}

Disable all hooks temporarily without deleting config:

json
{ "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

TypePurpose
commandRun a shell script. Event JSON on stdin, decisions on stdout

SDK-only

TypePurpose
httpPOST event JSON to a webhook URL. Available via hookCallbacks.
promptAsk an LLM to make a judgment call. Available via hookCallbacks.

Command hook

json
{
  "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):

json
{ "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

EventWhenCan block?
SessionStartSession begins or resumesNo
SessionEndSession terminatesNo
PreCompactBefore context compactionNo
UserPromptSubmitUser submits a promptYes (exit 2)
StopBefore model concludes responseYes (exit 2 or JSON)

Tool events

EventWhenCan block?
PreToolUseBefore tool executesYes
PostToolUseAfter tool succeedsLimited
PostToolUseFailureAfter tool failsLimited
PermissionRequestPermission dialog shownYes
NotificationBefore a notification is shownNo

Agent & team events

EventWhen
SubagentStartSubagent spawned
SubagentStopSubagent finishes
TeammateIdleBackground agent becomes idle
TaskCreatedTask added to shared list
TaskCompletedTask marked done

Input/output contract

Exit codes (command hooks)

CodeMeaningBehavior
0SuccessParse stdout as JSON for decisions
1Non-blocking errorContinue; stderr logged
2Blocking errorBlock the action; stderr fed to model

JSON output

json
{
  "continue": true,
  "decision": "allow",
  "reason": "explanation"
}

Common input fields (all events)

json
{
  "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: true on 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:

typescript
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

FieldEffect
shouldSkipSkip this tool call (PreToolUse only)
shouldInterruptStop the agent immediately
suppressOutputSuppress tool output from conversation
messageFeedback 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)

Released under the Apache-2.0 License.