本文档参考说明了可用的钩子类型及示例,包括它们的输入和输出格式、脚本最佳实践以及用于日志记录、安全强制和外部集成的高级模式。有关创建钩子的一般信息,请参阅 使用 GitHub Copilot 代理的钩子。有关为 CLI 创建钩子的教程,请参阅 使用 Copilot CLI 的钩子实现可预测、符合策略的执行。
钩子类型
会话启动钩子
在新代理会话开始或恢复现有会话时执行。
输入 JSON
{
"timestamp": 1704614400000,
"cwd": "/path/to/project",
"source": "new",
"initialPrompt": "Create a new feature"
}
{
"timestamp": 1704614400000,
"cwd": "/path/to/project",
"source": "new",
"initialPrompt": "Create a new feature"
}
字段
timestamp: 以毫秒为单位的 Unix 时间戳cwd: 当前工作目录source: 可以是"new"(新会话),"resume"(恢复会话),或"startup"initialPrompt: 用户的初始提示(如果提供)
输出: 被忽略(不处理返回值)
示例钩子
{
"type": "command",
"bash": "./scripts/session-start.sh",
"powershell": "./scripts/session-start.ps1",
"cwd": "scripts",
"timeoutSec": 30
}
{
"type": "command",
"bash": "./scripts/session-start.sh",
"powershell": "./scripts/session-start.ps1",
"cwd": "scripts",
"timeoutSec": 30
}
示例脚本(Bash)
#!/bin/bash INPUT=$(cat) SOURCE=$(echo "$INPUT" | jq -r '.source') TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp') echo "Session started from $SOURCE at $TIMESTAMP" >> session.log
#!/bin/bash
INPUT=$(cat)
SOURCE=$(echo "$INPUT" | jq -r '.source')
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
echo "Session started from $SOURCE at $TIMESTAMP" >> session.log
会话结束钩子
在代理会话完成或被终止时执行。
输入 JSON
{
"timestamp": 1704618000000,
"cwd": "/path/to/project",
"reason": "complete"
}
{
"timestamp": 1704618000000,
"cwd": "/path/to/project",
"reason": "complete"
}
字段
timestamp: 以毫秒为单位的 Unix 时间戳cwd: 当前工作目录reason: 以下之一"complete"、"error"、"abort"、"timeout"或"user_exit"
输出: 被忽略
示例脚本
#!/bin/bash INPUT=$(cat) REASON=$(echo "$INPUT" | jq -r '.reason') echo "Session ended: $REASON" >> session.log # Cleanup temporary files rm -rf /tmp/session-*
#!/bin/bash
INPUT=$(cat)
REASON=$(echo "$INPUT" | jq -r '.reason')
echo "Session ended: $REASON" >> session.log
# Cleanup temporary files
rm -rf /tmp/session-*
用户提交提示钩子
当用户向代理提交提示时执行。
输入 JSON
{
"timestamp": 1704614500000,
"cwd": "/path/to/project",
"prompt": "Fix the authentication bug"
}
{
"timestamp": 1704614500000,
"cwd": "/path/to/project",
"prompt": "Fix the authentication bug"
}
字段
timestamp: 以毫秒为单位的 Unix 时间戳cwd: 当前工作目录prompt: 用户提交的完整文本
输出: 被忽略(当前不支持在客户钩子中修改提示)
示例脚本
#!/bin/bash INPUT=$(cat) PROMPT=$(echo "$INPUT" | jq -r '.prompt') TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp') # Log to a structured file echo "$(date -d @$((TIMESTAMP/1000))): $PROMPT" >> prompts.log
#!/bin/bash
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt')
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
# Log to a structured file
echo "$(date -d @$((TIMESTAMP/1000))): $PROMPT" >> prompts.log
工具使用前钩子
在代理使用任何工具(如 bash、edit、view)之前执行。这是最强大的钩子,因为它可以批准或拒绝工具执行。
输入 JSON
{
"timestamp": 1704614600000,
"cwd": "/path/to/project",
"toolName": "bash",
"toolArgs": "{\"command\":\"rm -rf dist\",\"description\":\"Clean build directory\"}"
}
{
"timestamp": 1704614600000,
"cwd": "/path/to/project",
"toolName": "bash",
"toolArgs": "{\"command\":\"rm -rf dist\",\"description\":\"Clean build directory\"}"
}
字段
timestamp: 以毫秒为单位的 Unix 时间戳cwd: 当前工作目录toolName: 被调用的工具名称(例如 "bash", "edit", "view", "create")toolArgs: 包含工具参数的 JSON 字符串
输出 JSON(可选)
{
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive operations require approval"
}
{
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive operations require approval"
}
输出字段
permissionDecision: 可以是"allow"、"deny"或"ask"(目前仅处理"deny")permissionDecisionReason: 对决定的可读解释
示例钩子:阻止危险命令
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TOOL_ARGS=$(echo "$INPUT" | jq -r '.toolArgs')
# Log the tool use
echo "$(date): Tool=$TOOL_NAME Args=$TOOL_ARGS" >> tool-usage.log
# Check for dangerous patterns
if echo "$TOOL_ARGS" | grep -qE "rm -rf /|format|DROP TABLE"; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Dangerous command detected"}'
exit 0
fi
# Allow by default (or omit output to allow)
echo '{"permissionDecision":"allow"}'
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TOOL_ARGS=$(echo "$INPUT" | jq -r '.toolArgs')
# Log the tool use
echo "$(date): Tool=$TOOL_NAME Args=$TOOL_ARGS" >> tool-usage.log
# Check for dangerous patterns
if echo "$TOOL_ARGS" | grep -qE "rm -rf /|format|DROP TABLE"; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Dangerous command detected"}'
exit 0
fi
# Allow by default (or omit output to allow)
echo '{"permissionDecision":"allow"}'
示例钩子:强制文件权限
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
# Only allow editing specific directories
if [ "$TOOL_NAME" = "edit" ]; then
PATH_ARG=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.path')
if [[ ! "$PATH_ARG" =~ ^(src/|test/) ]]; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Can only edit files in src/ or test/ directories"}'
exit 0
fi
fi
# Allow all other tools
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
# Only allow editing specific directories
if [ "$TOOL_NAME" = "edit" ]; then
PATH_ARG=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.path')
if [[ ! "$PATH_ARG" =~ ^(src/|test/) ]]; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Can only edit files in src/ or test/ directories"}'
exit 0
fi
fi
# Allow all other tools
工具使用后钩子
在工具执行完毕后执行(无论成功或失败)。
示例输入 JSON
{
"timestamp": 1704614700000,
"cwd": "/path/to/project",
"toolName": "bash",
"toolArgs": "{\"command\":\"npm test\"}",
"toolResult": {
"resultType": "success",
"textResultForLlm": "All tests passed (15/15)"
}
}
{
"timestamp": 1704614700000,
"cwd": "/path/to/project",
"toolName": "bash",
"toolArgs": "{\"command\":\"npm test\"}",
"toolResult": {
"resultType": "success",
"textResultForLlm": "All tests passed (15/15)"
}
}
字段
timestamp: 以毫秒为单位的 Unix 时间戳cwd: 当前工作目录toolName: 已执行的工具名称toolArgs: 包含工具参数的 JSON 字符串toolResult: 包含以下内容的结果对象resultType: 可以是"success"、"failure"或"denied"textResultForLlm: 显示给代理的结果文本
输出: 被忽略(当前不支持结果修改)
示例脚本:将工具执行统计记录到 CSV 文件
此脚本将工具执行统计记录到 CSV 文件,并在工具失败时发送电子邮件警报。
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
RESULT_TYPE=$(echo "$INPUT" | jq -r '.toolResult.resultType')
# Track statistics
echo "$(date),${TOOL_NAME},${RESULT_TYPE}" >> tool-stats.csv
# Alert on failures
if [ "$RESULT_TYPE" = "failure" ]; then
RESULT_TEXT=$(echo "$INPUT" | jq -r '.toolResult.textResultForLlm')
echo "FAILURE: $TOOL_NAME - $RESULT_TEXT" | mail -s "Agent Tool Failed" admin@example.com
fi
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
RESULT_TYPE=$(echo "$INPUT" | jq -r '.toolResult.resultType')
# Track statistics
echo "$(date),${TOOL_NAME},${RESULT_TYPE}" >> tool-stats.csv
# Alert on failures
if [ "$RESULT_TYPE" = "failure" ]; then
RESULT_TEXT=$(echo "$INPUT" | jq -r '.toolResult.textResultForLlm')
echo "FAILURE: $TOOL_NAME - $RESULT_TEXT" | mail -s "Agent Tool Failed" admin@example.com
fi
错误发生钩子
在代理执行期间出现错误时执行。
示例输入 JSON
{
"timestamp": 1704614800000,
"cwd": "/path/to/project",
"error": {
"message": "Network timeout",
"name": "TimeoutError",
"stack": "TimeoutError: Network timeout\n at ..."
}
}
{
"timestamp": 1704614800000,
"cwd": "/path/to/project",
"error": {
"message": "Network timeout",
"name": "TimeoutError",
"stack": "TimeoutError: Network timeout\n at ..."
}
}
字段
timestamp: 以毫秒为单位的 Unix 时间戳cwd: 当前工作目录error: 包含以下内容的错误对象message: 错误信息name: 错误类型/名称stack: 堆栈跟踪(如果可用)
输出: 被忽略(当前不支持错误处理修改)
示例脚本:将错误详情提取到日志文件
#!/bin/bash INPUT=$(cat) ERROR_MSG=$(echo "$INPUT" | jq -r '.error.message') ERROR_NAME=$(echo "$INPUT" | jq -r '.error.name') echo "$(date): [$ERROR_NAME] $ERROR_MSG" >> errors.log
#!/bin/bash
INPUT=$(cat)
ERROR_MSG=$(echo "$INPUT" | jq -r '.error.message')
ERROR_NAME=$(echo "$INPUT" | jq -r '.error.name')
echo "$(date): [$ERROR_NAME] $ERROR_MSG" >> errors.log
脚本最佳实践
读取输入
此示例脚本从 stdin 读取 JSON 输入到变量中,然后使用 jq 提取 timestamp 和 cwd 字段。
Bash
#!/bin/bash # Read JSON from stdin INPUT=$(cat) # Parse with jq TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp') CWD=$(echo "$INPUT" | jq -r '.cwd')
#!/bin/bash
# Read JSON from stdin
INPUT=$(cat)
# Parse with jq
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
CWD=$(echo "$INPUT" | jq -r '.cwd')
PowerShell
# Read JSON from stdin $input = [Console]::In.ReadToEnd() | ConvertFrom-Json # Access properties $timestamp = $input.timestamp $cwd = $input.cwd
# Read JSON from stdin
$input = [Console]::In.ReadToEnd() | ConvertFrom-Json
# Access properties
$timestamp = $input.timestamp
$cwd = $input.cwd
输出 JSON
此示例脚本展示了如何从钩子脚本输出有效的 JSON。 在 Bash 中使用 jq -c 进行紧凑的单行输出,或在 PowerShell 中使用 ConvertTo-Json -Compress。
Bash
#!/bin/bash
# Use jq to compact the JSON output to a single line
echo '{"permissionDecision":"deny","permissionDecisionReason":"Security policy violation"}' | jq -c
# Or construct with variables
REASON="Too dangerous"
jq -n --arg reason "$REASON" '{permissionDecision: "deny", permissionDecisionReason: $reason}'
#!/bin/bash
# Use jq to compact the JSON output to a single line
echo '{"permissionDecision":"deny","permissionDecisionReason":"Security policy violation"}' | jq -c
# Or construct with variables
REASON="Too dangerous"
jq -n --arg reason "$REASON" '{permissionDecision: "deny", permissionDecisionReason: $reason}'
PowerShell
# Use ConvertTo-Json to compact the JSON output to a single line
$output = @{
permissionDecision = "deny"
permissionDecisionReason = "Security policy violation"
}
$output | ConvertTo-Json -Compress
# Use ConvertTo-Json to compact the JSON output to a single line
$output = @{
permissionDecision = "deny"
permissionDecisionReason = "Security policy violation"
}
$output | ConvertTo-Json -Compress
错误处理
此脚本示例演示了如何在钩子脚本中处理错误。
Bash
#!/bin/bash set -e # Exit on error INPUT=$(cat) # ... process input ... # Exit with 0 for success exit 0
#!/bin/bash
set -e # Exit on error
INPUT=$(cat)
# ... process input ...
# Exit with 0 for success
exit 0
PowerShell
$ErrorActionPreference = "Stop"
try {
$input = [Console]::In.ReadToEnd() | ConvertFrom-Json
# ... process input ...
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
$ErrorActionPreference = "Stop"
try {
$input = [Console]::In.ReadToEnd() | ConvertFrom-Json
# ... process input ...
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
处理超时
钩子默认超时为 30 秒。对于更长的操作,请增加 timeoutSec。
{
"type": "command",
"bash": "./scripts/slow-validation.sh",
"timeoutSec": 120
}
{
"type": "command",
"bash": "./scripts/slow-validation.sh",
"timeoutSec": 120
}
高级模式
同一类型的多个钩子
您可以为同一事件定义多个钩子。它们按顺序执行。
{
"version": 1,
"hooks": {
"preToolUse": [
{
"type": "command",
"bash": "./scripts/security-check.sh",
"comment": "Security validation - runs first"
},
{
"type": "command",
"bash": "./scripts/audit-log.sh",
"comment": "Audit logging - runs second"
},
{
"type": "command",
"bash": "./scripts/metrics.sh",
"comment": "Metrics collection - runs third"
}
]
}
}
{
"version": 1,
"hooks": {
"preToolUse": [
{
"type": "command",
"bash": "./scripts/security-check.sh",
"comment": "Security validation - runs first"
},
{
"type": "command",
"bash": "./scripts/audit-log.sh",
"comment": "Audit logging - runs second"
},
{
"type": "command",
"bash": "./scripts/metrics.sh",
"comment": "Metrics collection - runs third"
}
]
}
}
脚本中的条件逻辑
示例:仅阻止特定工具
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
# Only validate bash commands
if [ "$TOOL_NAME" != "bash" ]; then
exit 0 # Allow all non-bash tools
fi
# Check bash command for dangerous patterns
COMMAND=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.command')
if echo "$COMMAND" | grep -qE "rm -rf|sudo|mkfs"; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Dangerous system command"}'
fi
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
# Only validate bash commands
if [ "$TOOL_NAME" != "bash" ]; then
exit 0 # Allow all non-bash tools
fi
# Check bash command for dangerous patterns
COMMAND=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.command')
if echo "$COMMAND" | grep -qE "rm -rf|sudo|mkfs"; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Dangerous system command"}'
fi
结构化日志
示例:JSON Lines 格式
#!/bin/bash
INPUT=$(cat)
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
RESULT_TYPE=$(echo "$INPUT" | jq -r '.toolResult.resultType')
# Output structured log entry
jq -n \
--arg ts "$TIMESTAMP" \
--arg tool "$TOOL_NAME" \
--arg result "$RESULT_TYPE" \
'{timestamp: $ts, tool: $tool, result: $result}' >> logs/audit.jsonl
#!/bin/bash
INPUT=$(cat)
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
RESULT_TYPE=$(echo "$INPUT" | jq -r '.toolResult.resultType')
# Output structured log entry
jq -n \
--arg ts "$TIMESTAMP" \
--arg tool "$TOOL_NAME" \
--arg result "$RESULT_TYPE" \
'{timestamp: $ts, tool: $tool, result: $result}' >> logs/audit.jsonl
与外部系统集成
示例:向 Slack 发送警报
#!/bin/bash
INPUT=$(cat)
ERROR_MSG=$(echo "$INPUT" | jq -r '.error.message')
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Agent Error: $ERROR_MSG\"}"
#!/bin/bash
INPUT=$(cat)
ERROR_MSG=$(echo "$INPUT" | jq -r '.error.message')
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Agent Error: $ERROR_MSG\"}"
示例使用场景
合规审计轨迹
通过使用日志脚本记录所有代理操作以满足合规要求
{
"version": 1,
"hooks": {
"sessionStart": [{"type": "command", "bash": "./audit/log-session-start.sh"}],
"userPromptSubmitted": [{"type": "command", "bash": "./audit/log-prompt.sh"}],
"preToolUse": [{"type": "command", "bash": "./audit/log-tool-use.sh"}],
"postToolUse": [{"type": "command", "bash": "./audit/log-tool-result.sh"}],
"sessionEnd": [{"type": "command", "bash": "./audit/log-session-end.sh"}]
}
}
{
"version": 1,
"hooks": {
"sessionStart": [{"type": "command", "bash": "./audit/log-session-start.sh"}],
"userPromptSubmitted": [{"type": "command", "bash": "./audit/log-prompt.sh"}],
"preToolUse": [{"type": "command", "bash": "./audit/log-tool-use.sh"}],
"postToolUse": [{"type": "command", "bash": "./audit/log-tool-result.sh"}],
"sessionEnd": [{"type": "command", "bash": "./audit/log-session-end.sh"}]
}
}
成本跟踪
跟踪工具使用情况以进行成本分配
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
USER=${USER:-unknown}
echo "$TIMESTAMP,$USER,$TOOL_NAME" >> /var/log/copilot/usage.csv
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp')
USER=${USER:-unknown}
echo "$TIMESTAMP,$USER,$TOOL_NAME" >> /var/log/copilot/usage.csv
代码质量强制
阻止违反代码标准的提交
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
if [ "$TOOL_NAME" = "edit" ] || [ "$TOOL_NAME" = "create" ]; then
# Run linter before allowing edits
npm run lint-staged
if [ $? -ne 0 ]; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Code does not pass linting"}'
fi
fi
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
if [ "$TOOL_NAME" = "edit" ] || [ "$TOOL_NAME" = "create" ]; then
# Run linter before allowing edits
npm run lint-staged
if [ $? -ne 0 ]; then
echo '{"permissionDecision":"deny","permissionDecisionReason":"Code does not pass linting"}'
fi
fi
通知系统
在重要事件上发送通知
#!/bin/bash INPUT=$(cat) PROMPT=$(echo "$INPUT" | jq -r '.prompt') # Notify on production-related prompts if echo "$PROMPT" | grep -iq "production"; then echo "ALERT: Production-related prompt: $PROMPT" | mail -s "Agent Alert" team@example.com fi
#!/bin/bash
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt')
# Notify on production-related prompts
if echo "$PROMPT" | grep -iq "production"; then
echo "ALERT: Production-related prompt: $PROMPT" | mail -s "Agent Alert" team@example.com
fi