LV.1
EXP 0/1000
โ—€ Playbook index
NO.7.5

๐Ÿช Hooks

In a nutshell

Hooks inject custom scripts into the Copilot agent execution lifecycle and capture 6 events: session start / prompt submission / pre- and post-tool execution / error / session end.

๐Ÿง  Instructions are โ€œrequestsโ€ that rely on the agentโ€™s judgment, whereas hooks stop the execution logic itself. If you need policy enforcement, hooks are the only choice.

The 6 hook types

HookWhen it runsInput (CLI / Cloud Agent)What you can do
๐ŸŸข sessionStartNew / resume / startupsource, initialPromptLog initialization, environment setup, notification
๐Ÿ“ userPromptSubmittedThe moment the user submits a promptpromptPrompt audit log, keyword alerts
๐Ÿ›ก๏ธ preToolUse โ˜…Right before tool executiontoolName, toolArgsBlock execution with deny ยท allow ยท ask
๐Ÿ“Š postToolUseRight after tool executiontoolResultResult logging, failure notifications, statistics
๐Ÿ’ฅ errorOccurredWhen the agent crashes with an errorerrorSlack/email notification, incident log
๐Ÿ”š sessionEndWhen the session endsreasonCleanup, summary dispatch

๐Ÿ”‘ Only PreToolUse can stop the agent in its tracks. Think of the other five as โ€œobserve / record / notifyโ€ hooks.

Configuration

Place one or more .json files with any name under .github/hooks/. CLI / VS Code read locally; Cloud Agent reads from .github/hooks/ on the default branch.

{
  "version": 1,
  "hooks": {
    "preToolUse": [
      {
        "type": "command",
        "bash": "./scripts/guard.sh",
        "powershell": "./scripts/guard.ps1",
        "cwd": ".",
        "timeoutSec": 30,
        "env": { "LOG_LEVEL": "INFO" }
      }
    ]
  }
}
  • ๐Ÿ“ฆ Write both bash and powershell for cross-OS compatibility
  • โฑ๏ธ Default timeout is 30 seconds. Raise timeoutSec for heavy validations
  • ๐Ÿ” Stacking multiple hooks in an array for the same event runs them top to bottom
  • ๐Ÿ“ฅ Scripts receive JSON on stdin and optionally return JSON on stdout

โ˜… Blocking specific commands (PreToolUse)

The script receives what the agent is about to run as toolName and toolArgs, and can stop execution by returning {"permissionDecision": "deny", "permissionDecisionReason": "..."}.

#!/bin/bash
# .github/hooks/scripts/guard.sh
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TOOL_ARGS=$(echo "$INPUT" | jq -r '.toolArgs')

# Pass through anything that isn't bash
if [ "$TOOL_NAME" != "bash" ]; then
  exit 0
fi

COMMAND=$(echo "$TOOL_ARGS" | jq -r '.command')

# ๐Ÿšจ Blocklist of dangerous commands
if echo "$COMMAND" | grep -qE 'rm -rf /|sudo |mkfs|dd if=|:\(\)\{'; then
  jq -nc \
    --arg reason "Forbidden command: $COMMAND" \
    '{permissionDecision: "deny", permissionDecisionReason: $reason}'
  exit 0
fi

# ๐Ÿ”’ Block changes to production environments
if echo "$COMMAND" | grep -qE 'kubectl .*--context[= ]prod|terraform apply.*prod'; then
  jq -nc '{permissionDecision: "deny", permissionDecisionReason: "Changes to production require human review"}'
  exit 0
fi

# Allow everything else (no output or "allow")
exit 0

Where are hooks loaded from?

AgentWhere hooks are read fromScope
๐Ÿ’ป Copilot CLI.github/hooks/*.json in the current directoryOnly your CLI session
๐Ÿง‘โ€๐Ÿ’ป VS Code agent.github/hooks/*.json in the open workspaceOnly your VS Code agent session
โ˜๏ธ Copilot Cloud Agent.github/hooks/*.json on the default branch in GitHubAll Cloud Agent sessions for that repo

๐Ÿ“ Watch the surface differences: hooks.json uses timeout(VS Code) vs timeoutSec(CLI/Cloud), and scripts use tool_name / tool_input / hookSpecificOutput(VS Code) vs toolName / toolArgs / permissionDecision(CLI/Cloud).