Skip to content

Latest commit

 

History

History
507 lines (410 loc) · 15.7 KB

File metadata and controls

507 lines (410 loc) · 15.7 KB
title GitHub Copilot CLI hooks reference
shortTitle CLI hooks reference
intro Find hook events, configuration formats, and input payloads for {% data variables.copilot.copilot_cli_short %}.
versions
feature
copilot
category
Author and optimize with Copilot
Configure Copilot CLI
contentType reference
docsTeamMetrics
copilot-cli

Hooks are external commands that execute at specific lifecycle points during a session, enabling custom automation, security controls, and integrations. Hook configuration files are loaded automatically from .github/hooks/*.json in your repository.

Hook configuration format

Hook configuration files use JSON format with version 1.

Command hooks

Command hooks run shell scripts and are supported on all hook types.

{
  "version": 1,
  "hooks": {
    "preToolUse": [
      {
        "type": "command",
        "bash": "your-bash-command",
        "powershell": "your-powershell-command",
        "cwd": "optional/working/directory",
        "env": { "VAR": "value" },
        "timeoutSec": 30
      }
    ]
  }
}
Field Type Required Description
bash string One of bash/powershell Shell command for Unix.
cwd string No Working directory for the command (relative to repository root or absolute).
env object No Environment variables to set (supports variable expansion).
powershell string One of bash/powershell Shell command for Windows.
timeoutSec number No Timeout in seconds. Default: 30.
type "command" Yes Must be "command".

Prompt hooks

Prompt hooks auto-submit text as if the user typed it. They are only supported on sessionStart and only fire for new interactive sessions. They do not fire on resume, and they do not fire in non-interactive prompt mode (-p). The text can be a natural language prompt or a slash command.

{
  "version": 1,
  "hooks": {
    "sessionStart": [
      {
        "type": "prompt",
        "prompt": "Your prompt text or /slash-command"
      }
    ]
  }
}
Field Type Required Description
type "prompt" Yes Must be "prompt".
prompt string Yes Text to submit—can be a natural language message or a slash command.

Hook events

Event Fires when Output processed
agentStop The main agent finishes a turn. Yes — can block and force continuation.
errorOccurred An error occurs during execution. No
notification Fires asynchronously when the CLI emits a system notification (shell completion, agent completion or idle, permission prompts, elicitation dialogs). Fire-and-forget: never blocks the session. Supports matcher regex on notification_type. Optional — can inject additionalContext into the session.
permissionRequest Fires before the permission service runs (rules engine, session approvals, auto-allow/auto-deny, and user prompting). If the merged hook output returns behavior: "allow" or "deny", that decision short-circuits the normal permission flow. Supports matcher regex on toolName. Yes — can allow or deny programmatically.
postToolUse After each tool completes successfully. Yes — can replace the successful result (SDK programmatic hooks only).
postToolUseFailure After a tool completes with a failure. Yes — can provide recovery guidance via additionalContext (exit code 2 for command hooks).
preCompact Context compaction is about to begin (manual or automatic). Supports matcher to filter by trigger ("manual" or "auto"). No — notification only.
preToolUse Before each tool executes. Yes — can allow, deny, or modify.
sessionEnd The session terminates. No
sessionStart A new or resumed session begins. No
subagentStart A subagent is spawned (before it runs). Returns additionalContext prepended to the subagent's prompt. Supports matcher to filter by agent name. No — cannot block creation.
subagentStop A subagent completes. Yes — can block and force continuation.
userPromptSubmitted The user submits a prompt. No

Hook event input payloads

Each hook event delivers a JSON payload to the hook handler. Two payload formats are supported, selected by the event name used in the hook configuration:

  • camelCase format — Configure the event name in camelCase (for example, sessionStart). Fields use camelCase.
  • {% data variables.product.prodname_vscode_shortname %} compatible format — Configure the event name in PascalCase (for example, SessionStart). Fields use snake_case to match the {% data variables.product.prodname_vscode_shortname %} {% data variables.product.prodname_copilot_short %} extension format.

sessionStart / SessionStart

camelCase input:

{
    sessionId: string;
    timestamp: number;      // Unix timestamp in milliseconds
    cwd: string;
    source: "startup" | "resume" | "new";
    initialPrompt?: string;
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "SessionStart";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    source: "startup" | "resume" | "new";
    initial_prompt?: string;
}

sessionEnd / SessionEnd

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    reason: "complete" | "error" | "abort" | "timeout" | "user_exit";
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "SessionEnd";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    reason: "complete" | "error" | "abort" | "timeout" | "user_exit";
}

userPromptSubmitted / UserPromptSubmit

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    prompt: string;
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "UserPromptSubmit";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    prompt: string;
}

preToolUse / PreToolUse

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    toolName: string;
    toolArgs: unknown;
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

When configured with the PascalCase event name PreToolUse, the payload uses snake_case field names to match the {% data variables.product.prodname_vscode_shortname %} {% data variables.product.prodname_copilot_short %} extension format:

{
    hook_event_name: "PreToolUse";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    tool_name: string;
    tool_input: unknown;    // Tool arguments (parsed from JSON string when possible)
}

postToolUse / PostToolUse

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    toolName: string;
    toolArgs: unknown;
    toolResult: {
        resultType: "success";
        textResultForLlm: string;
    }
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "PostToolUse";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    tool_name: string;
    tool_input: unknown;
    tool_result: {
        result_type: "success" | "failure" | "denied" | "error";
        text_result_for_llm: string;
    }
}

postToolUseFailure / PostToolUseFailure

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    toolName: string;
    toolArgs: unknown;
    error: string;
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "PostToolUseFailure";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    tool_name: string;
    tool_input: unknown;
    error: string;
}

agentStop / Stop

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    transcriptPath: string;
    stopReason: "end_turn";
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "Stop";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    transcript_path: string;
    stop_reason: "end_turn";
}

subagentStart

Input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    transcriptPath: string;
    agentName: string;
    agentDisplayName?: string;
    agentDescription?: string;
}

subagentStop / SubagentStop

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    transcriptPath: string;
    agentName: string;
    agentDisplayName?: string;
    stopReason: "end_turn";
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "SubagentStop";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    transcript_path: string;
    agent_name: string;
    agent_display_name?: string;
    stop_reason: "end_turn";
}

errorOccurred / ErrorOccurred

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    error: {
        message: string;
        name: string;
        stack?: string;
    };
    errorContext: "model_call" | "tool_execution" | "system" | "user_input";
    recoverable: boolean;
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "ErrorOccurred";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    error: {
        message: string;
        name: string;
        stack?: string;
    };
    error_context: "model_call" | "tool_execution" | "system" | "user_input";
    recoverable: boolean;
}

preCompact / PreCompact

camelCase input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    transcriptPath: string;
    trigger: "manual" | "auto";
    customInstructions: string;
}

{% data variables.product.prodname_vscode_shortname %} compatible input:

{
    hook_event_name: "PreCompact";
    session_id: string;
    timestamp: string;      // ISO 8601 timestamp
    cwd: string;
    transcript_path: string;
    trigger: "manual" | "auto";
    custom_instructions: string;
}

preToolUse decision control

The preToolUse hook can control tool execution by writing a JSON object to stdout.

Field Values Description
permissionDecision "allow", "deny", "ask" Whether the tool executes. Empty output uses default behavior.
permissionDecisionReason string Reason shown to the agent. Required when decision is "deny".
modifiedArgs object Substitute tool arguments to use instead of the originals.

agentStop / subagentStop decision control

Field Values Description
decision "block", "allow" "block" forces another agent turn using reason as the prompt.
reason string Prompt for the next turn when decision is "block".

permissionRequest decision control

The permissionRequest hook fires before the permission service runs—before rule checks, session approvals, auto-allow/auto-deny, and user prompting. If hooks return behavior: "allow" or "deny", that decision short-circuits the normal permission flow. Returning nothing falls through to normal permission handling. Use it to approve or deny tool calls programmatically—especially useful in pipe mode (-p) and CI environments where no interactive prompt is available.

All configured permissionRequest hooks run for each request (except read and hook permission kinds, which short-circuit before hooks). Hook outputs are merged with later hook outputs overriding earlier ones.

Matcher: Optional regex tested against toolName. Anchored as ^(?:pattern)$; must match the full tool name. When set, the hook fires only for matching tool names.

Output JSON to stdout to control the permission decision:

Field Values Description
behavior "allow", "deny" Whether to approve or deny the tool call.
message string Reason fed back to the LLM when denying.
interrupt boolean When true combined with "deny", stops the agent entirely.

Return empty output or {} to fall through to the normal permission flow. For command hooks, exit code 2 is treated as a deny; stdout JSON (if any) is merged with {"behavior":"deny"}, and stderr is ignored.

notification hook

The notification hook fires asynchronously when the CLI emits a system notification. These hooks are fire-and-forget: they never block the session, and any errors are logged and skipped.

Input:

{
    sessionId: string;
    timestamp: number;
    cwd: string;
    hook_event_name: "Notification";
    message: string;           // Human-readable notification text
    title?: string;            // Short title (e.g., "Permission needed", "Shell completed")
    notification_type: string; // One of the types listed below
}

Notification types:

Type When it fires
shell_completed A background (async) shell command finishes
shell_detached_completed A detached shell session completes
agent_completed A background subagent finishes (completed or failed)
agent_idle A background agent finishes a turn and enters idle state (waiting for write_agent)
permission_prompt The agent requests permission to execute a tool
elicitation_dialog The agent requests additional information from the user

Output:

{
    additionalContext?: string; // Injected into the session as a user message
}

If additionalContext is returned, the text is injected into the session as a prepended user message. This can trigger further agent processing if the session is idle. Return {} or empty output to take no action.

Matcher: Optional regex on notification_type. The pattern is anchored as ^(?:pattern)$. Omit matcher to receive all notification types.

Tool names for hook matching

Tool name Description
ask_user Ask the user a clarifying question.
bash Execute shell commands (Unix).
create Create new files.
edit Modify file contents.
glob Find files by pattern.
grep Search file contents.
powershell Execute shell commands (Windows).
task Run subagent tasks.
view Read file contents.
web_fetch Fetch web pages.

If multiple hooks of the same type are configured, they execute in order. For preToolUse, if any hook returns "deny", the tool is blocked. For postToolUseFailure command hooks, exiting with code 2 causes stderr to be returned as recovery guidance for the assistant. Hook failures (non-zero exit codes or timeouts) are logged and skipped—they never block agent execution.

Further reading