Skip to main content

Hooks Reference

This document provides the technical specification for copilot-shell hooks, including JSON schemas and API details for all 13 wired events.

Global Hook Mechanics

  • Communication: stdin receives input (JSON), stdout outputs results (JSON), stderr outputs logs
  • Exit codes:
    • 0: Success; stdout is parsed as JSON
    • 2: System block; operation is aborted; stderr used as rejection reason
    • Other: Warning; non-fatal failure; CLI continues
  • Golden Rule: Scripts must not output anything to stdout other than JSON

Base Input Schema

All hooks receive the following common fields via stdin:

{
"session_id": "string",
"run_id": "string | undefined",
"transcript_path": "string",
"cwd": "string",
"hook_event_name": "string",
"timestamp": "string (ISO 8601)"
}
FieldTypeDescription
session_idstringUnique session identifier
run_idstring | undefinedCurrent agent run identifier (format: {sessionId}########{counter})
transcript_pathstringPath to the session JSONL transcript file
cwdstringCurrent working directory
hook_event_namestringName of the event that triggered this hook
timestampstringEvent trigger time (ISO 8601)

Common Output Fields

Most hooks support the following fields in the stdout JSON:

FieldTypeDescription
systemMessagestringNotification message shown to the user
suppressOutputbooleanWhen true, hides internal metadata
continuebooleanWhen false, immediately stops the agent loop
stopReasonstringReason shown to user when stopping
decisionstring"allow" / "deny" / "ask" / "approve"
reasonstringFeedback message when denying/blocking
hookSpecificOutputobjectEvent-specific output fields

Tool Hooks

PreToolUse

Triggered before tool execution. Used for argument validation, security checks, and parameter rewriting.

Input fields:

FieldTypeDescription
tool_use_idstringUnique tool call identifier
tool_namestringName of the tool being called
tool_inputobjectOriginal parameters generated by the model
mcp_contextobjectOptional MCP tool metadata
original_request_namestringOriginal name in tail calls

Output fields:

  • decision: Set to "deny" to block tool execution
  • systemMessage: Displayed as a standalone notification with hook name label
  • reason: Required when denying; sent to agent as tool error
  • hookSpecificOutput.tool_input: Merge-overwrites model parameters
  • continue: Set to false to terminate the entire agent loop

PostToolUse

Triggered after tool execution. Used for result auditing, context injection, or hiding sensitive output.

Input fields:

FieldTypeDescription
tool_use_idstringUnique tool call identifier
tool_namestringTool name
tool_inputobjectOriginal parameters
tool_responseobjectExecution result
mcp_contextobjectMCP metadata

Output fields:

  • decision: Set to "deny" to hide the real output
  • reason: When denied, replaces the tool result sent to the model
  • hookSpecificOutput.additionalContext: Appended to tool result
  • hookSpecificOutput.tailToolCallRequest: { name, args } to immediately execute another tool
  • continue: Set to false to terminate the agent loop

PostToolUseFailure

Triggered after tool execution failure. Used for error recovery and sandbox bypass.

Input fields:

FieldTypeDescription
tool_use_idstringUnique tool call identifier
tool_namestringTool name
tool_inputobjectOriginal parameters
errorstringError description
error_typestringError type (e.g., "timeout", "permission")
is_interruptbooleanWhether caused by user interrupt

Output fields:

  • hookSpecificOutput.additionalContext: Context to help the agent recover
  • hookSpecificOutput.sandbox_bypass_request: { original_command, reason } to request sandbox bypass

Agent Hooks

UserPromptSubmit

Triggered after user submits a prompt, before agent begins planning.

Input fields:

  • prompt: The raw text submitted by the user

Output fields:

  • hookSpecificOutput.additionalContext: Text appended to the current turn's prompt
  • decision: Set to "deny" to block the turn and discard the message
  • continue: Set to false to block the turn but keep the message
  • reason: Required when denying or stopping

Stop

Triggered when the agent is about to stop. Used for response validation and auto-retry.

Input fields:

  • stop_hook_active: Whether already in a retry sequence
  • last_assistant_message: The final text generated by the agent

Output fields:

  • decision: Set to "deny" to reject the response and force retry
  • reason: When denied, sent as a new prompt to the agent
  • continue: Set to false to stop the session
  • stopReason: Shown to user when stopping

Model Hooks

BeforeModel

Triggered before sending an LLM request. Uses a stable SDK-agnostic format via the Hook Translator.

Input fields:

  • llm_request: Contains model, messages, config, and optional toolConfig

Output fields:

  • hookSpecificOutput.llm_request: Overwrites partial request fields (e.g., swap model, adjust temperature)
  • hookSpecificOutput.llm_response: Synthetic response; when provided, skips the LLM call
  • decision: Set to "deny" to block this model request

BeforeToolSelection

Triggered before the LLM decides which tools to call. Used to filter available tool set.

Input fields:

  • llm_request: Same format as BeforeModel

Output fields:

  • hookSpecificOutput.toolConfig.mode: "AUTO" / "ANY" / "NONE"
    • "NONE": Disable all tools (highest priority)
    • "ANY": Force at least one tool call
  • hookSpecificOutput.toolConfig.allowedFunctionNames: Tool allowlist

Merge strategy: Multiple hooks' allowlists are unioned.

AfterModel

Triggered after receiving an LLM response. Used for observation, logging, or stop signals.

Input fields:

  • llm_request: Original request
  • llm_response: Model response

Output fields:

  • hookSpecificOutput.llm_response: Replaces stored history
  • decision: Set to "deny" to discard the response from history
  • continue: Set to false to stop after the current turn

Lifecycle & System Hooks

SessionStart

Triggered after app startup, session resume, or /clear command.

Input fields: source ("startup" / "resume" / "clear" / "compact")

Output fields:

  • hookSpecificOutput.additionalContext: Injected as first-turn content
  • systemMessage: Displayed at session start
  • Advisory only: continue and decision are ignored

SessionEnd

Triggered when the CLI exits or session is cleared.

Input fields: reason ("clear" / "logout" / "prompt_input_exit" / "other")

Output fields: systemMessage (displayed on close)

Notification

Triggered when the CLI issues a system notification (e.g., tool permission reminders).

Input fields:

  • notification_type: Notification type
  • message: Notification summary
  • details: Notification metadata

Advisory only; cannot block notifications.

PreCompact

Triggered before the CLI compresses history to save tokens.

Input fields: trigger ("auto" / "manual")

Advisory only; cannot block or modify the compression process.

PermissionRequest

Triggered when a permission dialog is displayed.

Input fields:

  • permission_mode: Current permission mode
  • tool_name: Tool name
  • tool_input: Tool parameters
  • permission_suggestions: Suggestion list

Output fields:

  • hookSpecificOutput.decision: { behavior: "allow"\|"deny", updatedInput?, message?, interrupt? }

Stable Model API

copilot-shell uses a Hook Translator layer to decouple hook scripts from the underlying SDK.

LLMRequest

{
"model": "string",
"messages": [
{ "role": "user | model | system", "content": "string" }
],
"config": {
"temperature": 0.7,
"maxOutputTokens": 8192,
"topP": 0.95,
"topK": 40
},
"toolConfig": {
"mode": "AUTO | ANY | NONE",
"allowedFunctionNames": ["read_file", "write_file"]
}
}

LLMResponse

{
"text": "string",
"candidates": [
{
"content": { "role": "model", "parts": ["text"] },
"finishReason": "STOP | MAX_TOKENS | SAFETY | OTHER",
"index": 0
}
],
"usageMetadata": {
"promptTokenCount": 100,
"candidatesTokenCount": 200,
"totalTokenCount": 300
}
}

Mode Priority (Multi-Hook Aggregation)

  • NONE always takes priority (most restrictive)
  • ANY > AUTO
  • allowedFunctionNames are unioned (sorted for determinism)

Hooks Reference

This document provides the technical specification for copilot-shell hooks, including JSON schemas and API details for 13 currently wired hook events.

Global hook mechanics

  • Communication: stdin for Input (JSON), stdout for Output (JSON), and stderr for logs and feedback.
  • Exit codes:
    • 0: Success. stdout is parsed as JSON. Preferred for all logic.
    • 2: System Block. The action is blocked; stderr is used as the rejection reason.
    • Other: Warning. A non-fatal failure occurred; the CLI continues with a warning.
  • Silence is Mandatory: Your script must not print any plain text to stdout other than the final JSON.

Base input schema

All hooks receive these common fields via stdin:

{
"session_id": "string",
"run_id": "string | undefined",
"transcript_path": "string",
"cwd": "string",
"hook_event_name": "string",
"timestamp": "string (ISO 8601)"
}
FieldTypeDescription
session_idstringUnique identifier for the CLI session (1 session = N runs).
run_idstring | undefinedUnique identifier for the current agent run (1 run = 1 user prompt → complete response). Format: {sessionId}########{counter}. Undefined for session-level events (SessionStart, SessionEnd) and for UserPromptSubmit (which fires before the run begins).
transcript_pathstringPath to the session's JSONL transcript file.
cwdstringCurrent working directory.
hook_event_namestringThe event that triggered this hook.
timestampstringISO 8601 timestamp of when the event fired.

Common output fields

Most hooks support these fields in their stdout JSON:

FieldTypeDescription
systemMessagestringShown to the user as a per-hook notification box labeled with the hook name, independent of the tool confirmation dialog.
suppressOutputbooleanIf true, hides internal hook metadata from logs/telemetry.
continuebooleanIf false, stops the entire agent loop immediately.
stopReasonstringDisplayed to the user when continue is false.
decisionstring"allow", "deny" (alias "block"), "ask", or "approve".
reasonstringThe feedback/error message used for "deny"/"block" decisions and stop-like flows. For user-visible allow/approve/ask messaging, prefer systemMessage; if omitted, reason is used as the fallback text for the notification.
hookSpecificOutputobjectEvent-specific output fields (see individual event sections).

Tool hooks

PreToolUse

Fires before a tool is invoked. Used for argument validation, security checks, and parameter rewriting.

  • Input Fields:
    • tool_use_id: (string) Optional unique identifier for the tool use. It is the same value exposed to PostToolUse, so hooks can correlate the before/after events for one tool call.
    • tool_name: (string) The name of the tool being called.
    • tool_input: (object) The raw arguments generated by the model.
    • mcp_context: (object) Optional metadata for MCP-based tools.
    • original_request_name: (string) The original name if this is a tail call.
  • Relevant Output Fields:
    • decision: Set to "deny" (or "block") to prevent tool execution.
    • systemMessage: Any informational or warning text you want the user to see. It is rendered as a separate, per-hook notification box (labeled with the hook name) above the tool confirmation dialog, regardless of the final decision. When the overall outcome is block/deny, per-hook boxes whose own decision is not blocking are dimmed so they do not visually conflict with the denied outcome.
    • reason: Required if denied. Sent to the agent as a tool error. Also used as the fallback notification text when systemMessage is omitted.
    • hookSpecificOutput.tool_input: An object that merges with and overrides the model's arguments before execution.
    • continue: Set to false to kill the entire agent loop.
  • Ask-dialog note: When decision is "ask", the tool confirmation dialog always uses a fixed prompt — A hook requires your confirmation to proceed. — instead of the hook's systemMessage. The hook's systemMessage is still shown as a separate notification box above the dialog, so users see both the reason and the confirmation choice.
  • Exit Code 2 (Block Tool): Prevents execution. Uses stderr as reason.

PostToolUse

Fires after a tool executes. Used for result auditing, context injection, or hiding sensitive output from the agent.

  • Input Fields:
    • tool_use_id: (string) Optional unique identifier for the tool use.
    • tool_name: (string)
    • tool_input: (object) The original arguments.
    • tool_response: (object) The result.
    • mcp_context: (object) Optional MCP metadata.
    • original_request_name: (string)
  • Relevant Output Fields:
    • decision: Set to "deny" to hide the real tool output from the agent.
    • reason: Required if denied. Replaces the tool result sent to model.
    • hookSpecificOutput.additionalContext: Appended to the tool result.
    • hookSpecificOutput.tailToolCallRequest: ({ name, args }) Execute another tool immediately; its result replaces the original response.
    • continue: Set to false to kill the agent loop.

PostToolUseFailure

Fires when a tool execution fails. Used for error recovery and sandbox bypass.

  • Input Fields:
    • tool_use_id: (string) Unique identifier for the tool use.
    • tool_name: (string)
    • tool_input: (object)
    • error: (string) Error message describing the failure.
    • error_type: (string) Type of error (e.g., "timeout", "permission").
    • is_interrupt: (boolean) Whether failure was caused by user interruption.
  • Relevant Output Fields:
    • hookSpecificOutput.additionalContext: Context to help the agent recover.
    • hookSpecificOutput.sandbox_bypass_request: ({ original_command, reason }) Request to bypass sandbox and re-run the original command.

Agent hooks

UserPromptSubmit

Fires after a user submits a prompt, before the agent begins planning. Used for prompt validation or injecting dynamic context.

  • Input Fields:
    • prompt: (string) The original text submitted by the user.
  • Relevant Output Fields:
    • hookSpecificOutput.additionalContext: Text appended to the prompt for this turn only.
    • decision: Set to "deny" to block the turn and discard the message.
    • continue: Set to false to block the turn but save the message.
    • reason: Required if denied or stopped.

Stop

Fires when the agent is about to stop. Used for response validation and automatic retries.

  • Input Fields:
    • stop_hook_active: (boolean) Indicates if already running as part of a retry sequence.
    • last_assistant_message: (string) The final text generated by the agent.
  • Relevant Output Fields:
    • decision: Set to "deny" to reject the response and force a retry.
    • reason: Required if denied. Sent to the agent as a new prompt.
    • continue: Set to false to stop the session.
    • stopReason: Displayed to the user when stopping.

Model hooks

BeforeModel

Fires before sending a request to the LLM. Operates on a stable, SDK-agnostic request format via the Hook Translator.

  • Input Fields:
    • llm_request: (object) Contains model, messages, config, and optional toolConfig.
  • Relevant Output Fields:
    • hookSpecificOutput.llm_request: An object that overrides parts of the outgoing request (e.g., changing models or temperature).
    • hookSpecificOutput.llm_response: A Synthetic Response object. If provided, the CLI skips the LLM call entirely and uses this as the response.
    • decision: Set to "deny" to block this model attempt. Without a synthetic response, the request path returns an empty stream.
  • Important Behavior Note: If blocked without a synthetic response, the empty stream may be handled by stream validation/retry logic rather than immediately terminating the turn.
  • Exit Code 2 (Block Turn): Treated as a blocking decision (deny) for this model attempt.

BeforeToolSelection

Fires before the LLM decides which tools to call. Used to filter the available toolset or force specific tool modes.

  • Input Fields:
    • llm_request: (object) Same format as BeforeModel.
  • Relevant Output Fields:
    • hookSpecificOutput.toolConfig.mode: ("AUTO" \| "ANY" \| "NONE")
      • "NONE": Disables all tools (wins over other hooks).
      • "ANY": Forces at least one tool call.
    • hookSpecificOutput.toolConfig.allowedFunctionNames: (string[]) Whitelist of tool names.
  • Union Strategy: Multiple hooks' whitelists are combined.
  • Limitations: Does not support decision, continue, or systemMessage.

AfterModel

Fires after receiving an LLM response. Used for real-time observation, logging, or stop signal.

  • Input Fields:
    • llm_request: (object) The original request.
    • llm_response: (object) The model's response.
  • Relevant Output Fields:
    • hookSpecificOutput.llm_response: An object that replaces the stored history entry for this turn. Note: streaming text already rendered to the terminal cannot be reverted; only the in-memory history (used for future context) is updated.
    • decision: Set to "deny" to discard the response from history and block the turn (prevents tool calls from executing).
    • continue: Set to false to stop the agent loop after the current turn.

Lifecycle & system hooks

SessionStart

Fires on application startup, resuming a session, or after a /clear command.

  • Input fields:
    • source: ("startup" \| "resume" \| "clear" \| "compact")
  • Relevant output fields:
    • hookSpecificOutput.additionalContext: Injected as the first turn.
    • systemMessage: Shown at the start of the session.
  • Advisory only: continue and decision fields are ignored.

SessionEnd

Fires when the CLI exits or a session is cleared.

  • Input Fields:
    • reason: ("clear" | "logout" | "prompt_input_exit" | "bypass_permissions_disabled" | "other")
  • Relevant Output Fields:
    • systemMessage: Displayed to the user during shutdown.
  • Execution Timing: SessionEnd is executed during shutdown and is awaited in normal cleanup paths. It remains non-blocking in intent (hook failures do not prevent process exit).

Notification

Fires when the CLI emits a system alert (e.g., Tool Permissions).

  • Input Fields:
    • notification_type: ("ToolPermission")
    • message: Summary of the alert.
    • details: JSON object with alert-specific metadata.
  • Relevant Output Fields:
    • systemMessage: Displayed alongside the system alert.
  • Observability Only: Cannot block alerts.

PreCompact

Fires before the CLI summarizes history to save tokens.

  • Input Fields:
    • trigger: ("auto" \| "manual")
    • custom_instructions: (string) Optional custom instructions.
  • Relevant Output Fields:
    • systemMessage: Displayed before compression.
  • Advisory Only: Cannot block or modify the compression process.

PermissionRequest

Fires when a permission dialog is displayed.

  • Input Fields:
    • permission_mode: (string) Current permission mode.
    • tool_name: (string)
    • tool_input: (object)
    • permission_suggestions: Array of { type, tool? } suggestions.
  • Relevant Output Fields:
    • hookSpecificOutput.decision: { behavior: "allow"|"deny", updatedInput?, updatedPermissions?, message?, interrupt? }

Stable Model API

copilot-shell uses a Hook Translator layer to decouple hook scripts from the underlying SDK types (@google/genai). This ensures hooks don't break across SDK updates.

LLMRequest

{
"model": "string",
"messages": [
{
"role": "user | model | system",
"content": "string (text-only, non-text parts are filtered)"
}
],
"config": {
"temperature": 0.7,
"maxOutputTokens": 8192,
"topP": 0.95,
"topK": 40
},
"toolConfig": {
"mode": "AUTO | ANY | NONE",
"allowedFunctionNames": ["read_file", "write_file"]
}
}

LLMResponse

{
"text": "string (convenience field, first candidate text)",
"candidates": [
{
"content": {
"role": "model",
"parts": ["text part 1", "text part 2"]
},
"finishReason": "STOP | MAX_TOKENS | SAFETY | RECITATION | OTHER",
"index": 0,
"safetyRatings": [
{ "category": "string", "probability": "string", "blocked": false }
]
}
],
"usageMetadata": {
"promptTokenCount": 100,
"candidatesTokenCount": 200,
"totalTokenCount": 300
}
}

HookToolConfig

{
"mode": "AUTO | ANY | NONE",
"allowedFunctionNames": ["tool_name_1", "tool_name_2"]
}

Mode priority (multi-hook aggregation)

When multiple hooks return different modes, they are aggregated:

  • NONE always wins (most restrictive)
  • ANY > AUTO
  • allowedFunctionNames are unioned across all hooks (sorted for deterministic behavior)