安全防护
阻止危险命令,防止敏感文件泄露。
Hooks 是在特定事件发生时自动运行的脚本,类似于 Git Hooks。你可以用它们来实现安全防护、代码质量检查、日志记录等自动化工作流。
安全防护
阻止危险命令,防止敏感文件泄露。
代码质量
自动格式化、lint 检查、运行测试。
上下文注入
在 Claude 处理请求前自动注入项目上下文。
日志审计
记录所有工具调用,追踪变更历史。
Hooks 通过事件系统工作。当 Claude Code 中发生特定事件时,对应的 Hook 脚本会自动触发。
| 事件 | 触发时机 | 可阻止? | 典型用途 |
|---|---|---|---|
SessionStart | 会话开始或恢复 | 否 | 初始化、加载开发上下文 |
UserPromptSubmit | 用户提交提示,Claude 处理前 | 是 | 上下文注入、提示验证 |
PreToolUse | 工具调用执行前 | 是 | 安全验证、输入修改 |
PermissionRequest | 权限对话框出现时 | 是 | 自定义审批逻辑 |
PostToolUse | 工具成功完成后 | 否 | 格式化、日志记录 |
PostToolUseFailure | 工具调用失败后 | 否 | 错误日志、恢复操作 |
Notification | Claude 发送通知 | 否 | 声音提醒、自定义通知 |
SubagentStart | 子 Agent 启动 | 否 | 子 Agent 初始化 |
SubagentStop | 子 Agent 完成 | 是 | 子 Agent 清理 |
Stop | Claude 完成响应 | 是 | 后续操作、继续循环 |
TeammateIdle | 团队 Agent 即将空闲 | 是 | 团队协调、质量把关 |
TaskCompleted | 任务标记完成 | 是 | 强制完成标准 |
ConfigChange | 配置文件在会话中更改 | 是(策略除外) | 企业审计、阻止未授权变更 |
WorktreeCreate | 创建 worktree | 是 | 自定义 VCS 设置 |
WorktreeRemove | 移除 worktree | 否 | 清理 VCS 状态 |
PreCompact | 上下文压缩前 | 否 | 压缩前保存状态 |
SessionEnd | 会话终止 | 否 | 清理、日志记录 |
用户输入消息 │ ▼┌──────────────────┐│ UserPromptSubmit │ ← 添加上下文(如 git status)└──────────────────┘ │ ▼Claude 决定运行工具(如 Edit) │ ▼┌──────────────────┐│ PreToolUse │ ← 安全检查└──────────────────┘ │ ▼(如果允许)工具执行 │ ▼┌──────────────────┐│ PostToolUse │ ← 自动格式化└──────────────────┘Claude Code 支持两种 Hook 执行模型:
async 或设置 async: falseasync: true| Hook 用途 | 执行模式 | 原因 |
|---|---|---|
| 代码格式化 (Prettier, Black) | 异步 | 外观变更,无需反馈 |
| Lint 自动修复 (eslint —fix) | 异步 | 非关键改进 |
| 类型检查 (tsc, mypy) | 同步 | 错误必须阻止后续操作 |
| 安全验证 | 同步 | 必须阻止危险操作 |
| 日志/指标 | 异步 | 纯副作用,无需反馈 |
| 通知 (Slack, 邮件) | 异步 | 用户提醒,非阻塞 |
| 测试执行 | 同步 | 结果影响下一步操作 |
| Git 上下文注入 | 同步 | 处理前丰富提示内容 |
并非所有场景都需要 AI。选择合适的工具:
| 任务类型 | 最佳工具 | 原因 | 示例 |
|---|---|---|---|
| 确定性的 | Bash 脚本 | 快速、可预测、不消耗 token | 创建分支、获取 PR 评论 |
| 基于模式的 | Bash + 正则 | 对已知模式可靠 | 检查密钥、验证格式 |
| 需要理解的 | AI Agent | 需要判断力 | 代码审查、架构决策 |
| 上下文依赖的 | AI Agent | 需要理解能力 | ”是否符合需求?” |
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/security-check.sh", "timeout": 5000 } ] } ] }}| 字段 | 说明 |
|---|---|
matcher | 正则模式,过滤 Hook 触发条件(工具名称等) |
type | Hook 类型:"command"、"http"、"prompt" 或 "agent" |
command | 要运行的 Shell 命令(command 类型) |
prompt | LLM 评估的提示文本(prompt/agent 类型)。使用 $ARGUMENTS 作为 Hook 输入 JSON 的占位符 |
timeout | 最大执行时间(秒)。默认:command 600s,prompt 30s,agent 60s |
model | 用于评估的模型(prompt/agent 类型)。默认为快速模型 |
async | 如为 true,在后台运行不阻塞(仅 command 类型) |
statusMessage | Hook 运行时显示的自定义加载消息 |
once | 如为 true,每个会话仅运行一次(仅限 skills) |
运行 Shell 命令。通过 stdin 接收 JSON,通过 stdout 返回 JSON。最常用的类型。
{ "type": "command", "command": ".claude/hooks/security-check.sh", "timeout": 5000}(v2.1.63+) 向 URL 发送 POST 请求并读取 JSON 响应。适用于 CI/CD Webhook 和无状态后端集成。
{ "type": "http", "url": "https://ci.example.com/webhook/claude-hook", "allowedEnvVars": ["CI_TOKEN"]}allowedEnvVars 字段列出可在 header 中引用的环境变量(如 Bearer token 认证)。
将提示和 Hook 输入发送给 Claude 模型(默认 Haiku)进行单轮评估。返回 {ok: true/false, reason: "..."}。
{ "type": "prompt", "prompt": "分析此工具调用是否安全: $ARGUMENTS", "model": "haiku"}生成具有工具访问权限的子 Agent(Read、Grep、Glob 等)进行多轮验证。最多 50 轮工具使用。返回相同的 {ok: true/false} 格式。
{ "type": "agent", "prompt": "验证此代码变更是否符合项目架构规范", "timeout": 60}Hook 通过 stdin 接收 JSON,包含通用字段和事件特定字段:
{ "session_id": "abc123", "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl", "cwd": "/project", "permission_mode": "default", "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": { "command": "git status" }}Hook 通过退出码和可选的 JSON 标准输出来传达结果。
通用 JSON 字段:
| 字段 | 默认值 | 说明 |
|---|---|---|
continue | true | 如果为 false,Claude 停止所有处理 |
stopReason | 无 | continue 为 false 时显示给用户的消息 |
suppressOutput | false | 如果为 true,在详细模式中隐藏输出 |
systemMessage | 无 | 显示给用户的警告消息 |
事件特定控制:
hookSpecificOutput 包含 permissionDecision(allow/deny/ask)、permissionDecisionReason、updatedInput、additionalContextdecision: "block" 和 reasonhookSpecificOutput 包含 decision.behavior(allow/deny)PreToolUse 阻止示例:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Hook 阻止了危险命令" }}PreToolUse 上下文注入:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "additionalContext": "当前 git 分支: feature/auth。3 个未提交的文件。" }}| 退出码 | 含义 | 结果 |
|---|---|---|
0 | 成功 | 允许操作,解析标准输出中的 JSON |
2 | 阻止错误 | 阻止操作(对于可阻止事件),stderr 反馈给 Claude |
| 其他 | 非阻止错误 | stderr 在详细模式中显示,继续执行 |
#!/bin/bash# 阻止危险命令
INPUT=$(cat)TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# 危险模式列表DANGEROUS_PATTERNS=( "rm -rf /" "rm -rf ~" "rm -rf *" "sudo rm" "git push --force origin main" "git push -f origin main" "npm publish" "> /dev/sda")
# 检查命令是否匹配危险模式for pattern in "${DANGEROUS_PATTERNS[@]}"; do if [[ "$COMMAND" == *"$pattern"* ]]; then echo "BLOCKED: 检测到危险命令: $pattern" >&2 exit 2 fidone
exit 0#!/bin/bash# 编辑后自动格式化代码
INPUT=$(cat)TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
# 仅对 Edit/Write 操作运行if [[ "$TOOL_NAME" != "Edit" && "$TOOL_NAME" != "Write" ]]; then exit 0fi
# 获取文件路径FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
# 无文件路径则跳过if [[ -z "$FILE_PATH" ]]; then exit 0fi
# 对支持的文件运行 Prettierif [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx|json|md|css|scss)$ ]]; then npx prettier --write "$FILE_PATH" 2>/dev/nullfi
exit 0#!/bin/bash# 为每个提示添加 git 上下文
# 获取 git 信息BRANCH=$(git branch --show-current 2>/dev/null || echo "不是 git 仓库")LAST_COMMIT=$(git log -1 --format='%h %s' 2>/dev/null || echo "无提交")STAGED=$(git diff --cached --stat 2>/dev/null | tail -1 || echo "无")UNSTAGED=$(git diff --stat 2>/dev/null | tail -1 || echo "无")
# 输出带上下文的 JSONcat << EOF{ "hookSpecificOutput": { "additionalContext": "[Git] 分支: $BRANCH | 最新: $LAST_COMMIT | 暂存: $STAGED | 未暂存: $UNSTAGED" }}EOF
exit 0#!/bin/bash# 在通知时播放声音 (macOS)
INPUT=$(cat)TITLE=$(echo "$INPUT" | jq -r '.title // ""')MESSAGE=$(echo "$INPUT" | jq -r '.message // ""')
# 根据内容决定声音if [[ "$TITLE" == *"error"* ]] || [[ "$MESSAGE" == *"failed"* ]]; then SOUND="/System/Library/Sounds/Basso.aiff"elif [[ "$TITLE" == *"complete"* ]] || [[ "$MESSAGE" == *"success"* ]]; then SOUND="/System/Library/Sounds/Hero.aiff"else SOUND="/System/Library/Sounds/Pop.aiff"fi
afplay "$SOUND" 2>/dev/null &exit 0# Windows 通知和声音提醒
$inputJson = [Console]::In.ReadToEnd() | ConvertFrom-Json$title = $inputJson.title$message = $inputJson.message
if ($title -match "error" -or $message -match "failed") { [System.Media.SystemSounds]::Hand.Play()} elseif ($title -match "complete" -or $message -match "success") { [System.Media.SystemSounds]::Asterisk.Play()} else { [System.Media.SystemSounds]::Beep.Play()}
exit 0# security-check.ps1 - 阻止危险命令
$inputJson = [Console]::In.ReadToEnd() | ConvertFrom-Json$command = $inputJson.tool_input.command
$dangerousPatterns = @( "rm -rf /", "rm -rf ~", "Remove-Item -Recurse -Force C:\", "git push --force origin main", "git push -f origin main", "npm publish")
foreach ($pattern in $dangerousPatterns) { if ($command -like "*$pattern*") { Write-Error "BLOCKED: 检测到危险命令: $pattern" exit 2 }}
exit 0# auto-format.ps1 - 编辑后自动格式化
$inputJson = [Console]::In.ReadToEnd() | ConvertFrom-Json$toolName = $inputJson.tool_name
if ($toolName -ne "Edit" -and $toolName -ne "Write") { exit 0}
$filePath = $inputJson.tool_input.file_path
if (-not $filePath) { exit 0}
if ($filePath -match '\.(ts|tsx|js|jsx|json|md|css|scss)$') { npx prettier --write $filePath 2>$null}
exit 0@echo offsetlocal enabledelayedexpansion
for /f "tokens=*" %%i in ('git branch --show-current 2^>nul') do set BRANCH=%%iif "%BRANCH%"=="" set BRANCH=not a git repo
for /f "tokens=*" %%i in ('git log -1 --format^="%%h %%s" 2^>nul') do set LAST_COMMIT=%%iif "%LAST_COMMIT%"=="" set LAST_COMMIT=no commits
echo {"hookSpecificOutput":{"additionalContext":"[Git] Branch: %BRANCH% | Last: %LAST_COMMIT%"}}exit /b 0Windows settings.json 配置:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|Edit|Write", "hooks": [ { "type": "command", "command": "powershell -ExecutionPolicy Bypass -File .claude/hooks/security-check.ps1", "timeout": 5000 } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "powershell -ExecutionPolicy Bypass -File .claude/hooks/auto-format.ps1", "timeout": 10000 } ] } ] }}安全 Hooks 是保护系统的关键防线。
#!/bin/bashINPUT=$(cat)COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# === 关键阻止 (Exit 2) ===
# 文件系统破坏[[ "$COMMAND" =~ rm.*-rf.*[/~] ]] && { echo "BLOCKED: 递归删除根目录/主目录" >&2; exit 2; }
# 磁盘操作[[ "$COMMAND" =~ ">/dev/sd" ]] && { echo "BLOCKED: 直接写入磁盘" >&2; exit 2; }[[ "$COMMAND" =~ "dd if=" ]] && { echo "BLOCKED: dd 命令" >&2; exit 2; }
# 受保护分支的 Git 强制操作[[ "$COMMAND" =~ "git push".*"-f".*"(main|master)" ]] && { echo "BLOCKED: 强制推送到 main" >&2; exit 2; }[[ "$COMMAND" =~ "git push --force".*"(main|master)" ]] && { echo "BLOCKED: 强制推送到 main" >&2; exit 2; }
# 包发布[[ "$COMMAND" =~ "npm publish" ]] && { echo "BLOCKED: npm publish" >&2; exit 2; }
# 特权操作[[ "$COMMAND" =~ ^sudo ]] && { echo "BLOCKED: sudo 命令" >&2; exit 2; }
# === 警告 (Exit 0 但记录日志) ===[[ "$COMMAND" =~ "rm -rf" ]] && echo "WARNING: 检测到递归删除" >&2
exit 0# 测试被阻止的命令echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | \ .claude/hooks/security-blocker.shecho "退出码: $?" # 应为 2
# 测试安全命令echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | \ .claude/hooks/security-blocker.shecho "退出码: $?" # 应为 0一种高级模式是使用更强大的模型作为安全门,而非仅依赖静态规则匹配:
# .claude/hooks/opus-security-gate.sh(概念性)# PreToolUse Hook - 路由到 Opus 进行安全筛查
INPUT=$(cat)TOOL=$(echo "$INPUT" | jq -r '.tool_name')COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# 快速通道:已知安全工具跳过检查[[ "$TOOL" == "Read" || "$TOOL" == "Grep" || "$TOOL" == "Glob" ]] && exit 0
# 路由到 Opus 进行安全分析VERDICT=$(echo "$INPUT" | claude --model opus --print \ "分析此工具调用的安全风险。安全则回复 SAFE,否则 BLOCKED:原因")
[[ "$VERDICT" == SAFE* ]] && exit 0echo "BLOCKED by security gate: $VERDICT" >&2exit 2保护敏感文件需要多层防护方法:
┌────────────────────────────────────────────┐│ 文件保护架构 │├────────────────────────────────────────────┤│ ││ 第 1 层: permissions.deny (内置) ││ ───────────────────────── ││ • 内置于 settings.json ││ • 无需 Hook ││ • 即时阻止所有工具访问 ││ • 适用于: 绝对禁止的文件 ││ ││ 第 2 层: 模式匹配 (Hook) ││ ───────────────────── ││ • 使用 .agentignore 模式的 PreToolUse Hook ││ • 支持 gitignore 风格语法 ││ • 适用于: 敏感文件类别 ││ ││ 第 3 层: 绕过检测 (Hook) ││ ───────────────────── ││ • 检测变量展开 ($VAR) ││ • 检测命令替换 $(cmd) ││ • 适用于: 防御复杂攻击 ││ │└────────────────────────────────────────────┘{ "permissions": { "deny": [ ".env", ".env.local", ".env.production", "**/*.key", "**/*.pem", "credentials.json", ".aws/credentials" ] }}优点:即时阻止,无需 Hook 缺点:无自定义逻辑,无法记录尝试
创建 .agentignore(或 .aiignore)在项目根目录:
# 凭据.env**.key*.pem*.p12credentials.jsonsecrets.yaml
# 配置config/secrets/.aws/credentials.ssh/id_*
# 构建产物dist/.envbuild/config/production.json优点:gitignore 语法熟悉,集中规则管理,版本控制 缺点:需要 Hook 实现
检测利用变量展开的复杂攻击尝试:
# 攻击示例FILE="sensitive.key"cat $FILE # 变量展开绕过
HOME_DIR=$HOMEcat $HOME_DIR/.env # 变量替换绕过
cat $(echo ".env") # 命令替换绕过检测逻辑:
detect_bypass() { local file="$1"
# 变量展开 [[ "$file" =~ \$\{?[A-Za-z_][A-Za-z0-9_]*\}? ]] && return 0
# 命令替换 [[ "$file" =~ \$\( || "$file" =~ \` ]] && return 0
return 1}配置 settings.json
{ "permissions": { "deny": [".env", "*.key", "*.pem"] }, "hooks": { "PreToolUse": [ { "matcher": "Read|Write|Edit", "hooks": [ { "type": "command", "command": ".claude/hooks/file-guard.sh", "timeout": 2000 } ] } ] }}创建 .agentignore
.env*config/secrets/**/*.key**/*.pemcredentials.json复制 Hook 模板
cp examples/hooks/bash/file-guard.sh .claude/hooks/chmod +x .claude/hooks/file-guard.sh测试保护
# 测试直接访问echo '{"tool_name":"Read","tool_input":{"file_path":".env"}}' | \ .claude/hooks/file-guard.sh# 应显示 "File access blocked"
# 测试绕过尝试echo '{"tool_name":"Read","tool_input":{"file_path":"$HOME/.env"}}' | \ .claude/hooks/file-guard.sh# 应显示 "Variable expansion detected"当 Hook 集合增长时,不要配置数十个独立 Hook,而是使用单一调度器根据文件类型、工具和上下文智能路由事件。
#!/bin/bash# 所有 PostToolUse Hook 的单一入口点
INPUT=$(cat)TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.command // ""')
HOOKS_DIR="$(dirname "$0")/handlers"
# 按文件扩展名路由case "$FILE_PATH" in *.ts|*.tsx) [[ -x "$HOOKS_DIR/typescript.sh" ]] && echo "$INPUT" | "$HOOKS_DIR/typescript.sh" ;; *.py) [[ -x "$HOOKS_DIR/python.sh" ]] && echo "$INPUT" | "$HOOKS_DIR/python.sh" ;; *.rs) [[ -x "$HOOKS_DIR/rust.sh" ]] && echo "$INPUT" | "$HOOKS_DIR/rust.sh" ;; *.sql|*.prisma) [[ -x "$HOOKS_DIR/database.sh" ]] && echo "$INPUT" | "$HOOKS_DIR/database.sh" ;;esac
# 按工具路由(始终运行,与文件类型无关)case "$TOOL_NAME" in Bash) [[ -x "$HOOKS_DIR/security.sh" ]] && echo "$INPUT" | "$HOOKS_DIR/security.sh" ;; Write) [[ -x "$HOOKS_DIR/new-file.sh" ]] && echo "$INPUT" | "$HOOKS_DIR/new-file.sh" ;;esac
exit 0settings.json 配置(极简):
{ "hooks": { "PostToolUse": [{ "matcher": "Edit|Write|Bash", "hooks": [{ "type": "command", "command": ".claude/hooks/dispatch.sh" }] }] }}Handler 目录结构:
.claude/hooks/├── dispatch.sh # 单一入口点└── handlers/ ├── typescript.sh # ESLint + tsc(.ts/.tsx) ├── python.sh # Ruff + mypy(.py) ├── rust.sh # cargo clippy(.rs) ├── database.sh # Schema 验证(.sql/.prisma) ├── security.sh # 阻止危险 Bash 命令 └── new-file.sh # Write 时检查命名规范#!/bin/bash# 将所有工具使用记录到 JSONL 文件
INPUT=$(cat)LOG_DIR="$HOME/.claude/logs"LOG_FILE="$LOG_DIR/activity-$(date +%Y-%m-%d).jsonl"
# 创建日志目录mkdir -p "$LOG_DIR"
# 清理旧日志(保留 7 天)find "$LOG_DIR" -name "activity-*.jsonl" -mtime +7 -delete
# 提取工具信息TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')
# 创建日志条目LOG_ENTRY=$(jq -n \ --arg timestamp "$TIMESTAMP" \ --arg tool "$TOOL_NAME" \ --arg session "$SESSION_ID" \ '{timestamp: $timestamp, tool: $tool, session: $session}')
# 追加到日志echo "$LOG_ENTRY" >> "$LOG_FILE"
exit 0#!/bin/bash# 代码变更后运行 linter
INPUT=$(cat)TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
# 仅在 Edit/Write 后运行if [[ "$TOOL_NAME" != "Edit" && "$TOOL_NAME" != "Write" ]]; then exit 0fi
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
# 仅 lint TypeScript/JavaScriptif [[ ! "$FILE_PATH" =~ \.(ts|tsx|js|jsx)$ ]]; then exit 0fi
# 运行 ESLintLINT_OUTPUT=$(npx eslint "$FILE_PATH" 2>&1)LINT_EXIT=$?
if [[ $LINT_EXIT -ne 0 ]]; then cat << EOF{ "systemMessage": "Lint errors found in $FILE_PATH:\n$LINT_OUTPUT"}EOFfi
exit 0将多个验证 Hook 串联起来,在代码变更后立即捕获问题:
Edit/Write → 类型检查 → Lint → 测试 → 通知 Claude ↓ ↓ ↓ ↓ file.ts tsc check eslint jest file.test.ts三阶段流水线配置:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/typecheck-on-save.sh", "timeout": 5000 }, { "type": "command", "command": ".claude/hooks/lint-gate.sh", "timeout": 5000 }, { "type": "command", "command": ".claude/hooks/test-on-change.sh", "timeout": 10000 } ] } ] }}测试文件检测逻辑:
| 源文件 | 测试文件模式 |
|---|---|
auth.ts | auth.test.ts、__tests__/auth.test.ts |
utils.py | utils_test.py、test_utils.py |
main.go | main_test.go |
性能考虑:
| 项目规模 | 流水线时间 | 可接受? |
|---|---|---|
| 小型 (<100 文件) | 约 1-2 秒/编辑 | 是 |
| 中型 (100-1000 文件) | 约 2-5 秒/编辑 | 是(增量编译) |
| 大型 (1000+ 文件) | 约 5-10 秒/编辑 | 考虑异步或跳过测试 |
优化策略:
async: true(外观检查)tsc --incremental)使用 Stop 事件在 Claude Code 结束时显示全面的会话统计信息:
{ "hooks": { "SessionEnd": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/session-summary.sh" }] }] }}示例输出:
═══ Session Summary ═══════════════════ID: abc-123-def-456Name: Security hardening v3.26Branch: mainDuration: Wall 1h 34m | Active 14m 24s
Tool Calls: 47 (OK 45 / ERR 2) Read: 12 Bash: 10 Edit: 8 Write: 6 Grep: 5 Glob: 4 WebSearch: 2
Model Usage Reqs Input Outputclaude-sonnet-4-5 42 493.9K 2.5Kclaude-haiku-4-5 5 12.4K 46
Cache: 1.2M read / 45.3K createdEst. Cost: $0.74═══════════════════════════════════════先测试再部署
使用 echo 管道 JSON 到 Hook 脚本进行测试,确认退出码正确后再在 Claude Code 中使用。
合理设置超时
为每个 Hook 设置合理的超时时间。安全检查 2-5 秒,测试 10 秒,格式化 5 秒。
使用调度器模式
当 Hook 数量增多时,使用单一调度器而非在 settings.json 中配置大量独立 Hook。
区分同步和异步
关键验证用同步,非关键操作用异步,平衡安全性和性能。