首页 › tutorials › Hooks拦截工具调用:造自动化安全护栏

概述

本教程带你掌握 Claude Code 的 Hooks(生命周期钩子) 系统——一种在工具执行、对话轮次、会话启停等关键节点自动触发用户脚本的能力。学完后你将能够:用 PreToolUse 在危险命令执行前拦下它、用 PostToolUse 在文件写入后自动格式化、用 UserPromptSubmit 给每条提问注入项目上下文、用 Notification 在 Claude 等待确认时桌面提醒。Hooks 是把 Claude Code 从"陪你聊天的助手"变成"受控的自动化执行体"的核心机制,适合已有基础配置经验、想让 Agent 真正无人值守且安全可控的开发者。

前置条件


正文

第一步:理解 Hooks 的三层运行节奏

Hooks 不是单一触发点,而是分布在 Claude Code 会话全生命周期的一组事件。按触发频率,它们归为三种节奏(cadence),记住这个分类,是设计护栏的前提:

flowchart TD
    S([会话开始]) --> SS[SessionStart<br/>每会话一次]
    SS --> Loop[进入对话轮次循环]
    Loop --> UPS[UserPromptSubmit<br/>每轮提问一次]
    UPS --> Agent[Agent 工具调用循环]
    Agent --> Pre{PreToolUse<br/>每次工具调用前}
    Pre -->|allow/放行| Exec[工具执行]
    Pre -->|deny/拦截| Block[阻断并反馈]
    Exec --> Post{PostToolUse<br/>每次工具调用后}
    Post --> Side[副作用:格式化/日志/校验]
    Side --> More{还有工具调用?}
    More -->|是| Pre
    More -->|否| Stop[Stop<br/>本轮回答结束]
    Block --> Stop
    Stop --> Loop
    Loop --> End([会话结束])
    End --> SE[SessionEnd<br/>每会话一次]

此外还有一批异步事件(async events),如 Notification、PreCompact、PostCompact、SubagentStart、SubagentStop、TaskCreated、TaskCompleted、ConfigChange、InstructionsLoaded、CwdChanged、FileChanged。它们不阻塞主流程,仅产生副作用(发通知、写日志)。

预期结果:在脑海里建立"会话→轮次→工具调用"的三层嵌套视图,知道每类钩子挂在哪一层。

第二步:掌握钩子的四种通信通道

Claude Code 与你的钩子脚本之间有四条通道,理解它们的协议是写出可靠钩子的关键:

通道 方向 用途
stdin Claude Code → 脚本 传入事件上下文(JSON),含工具名、参数、会话 ID、cwd 等
stdout 脚本 → Claude Code 传回 JSON 结构化决策(仅在 exit 0 时被解析)
stderr 脚本 → 用户 反馈信息,exit 2 时会展示给用户
exit code 脚本 → Claude Code 0 放行、2 阻断、1 非阻断错误

关键陷阱:Claude Code 仅在退出码为 0 时解析 stdout 中的 JSON;若 exit 2,任何 JSON 都被忽略。这与传统 Unix 习惯相反——exit 1 被当作"非阻断错误",Claude Code 照常继续执行动作。所以"想阻止一个动作",你必须用 exit 2 或 exit 0 + JSON permissionDecision: "deny",绝不能用 exit 1。

一个最小可运行的钩子脚本(读取 stdin、打印决策)长这样:

#!/usr/bin/env bash
# 读取 Claude Code 经 stdin 传入的事件 JSON
input=$(cat)

# 提取工具名(用 jq 解析)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')

if [ "$tool_name" = "Bash" ]; then
  cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
  # 检测危险命令
  if echo "$cmd" | grep -qE 'rm -rf /|sudo '; then
    echo "检测到危险命令: $cmd" >&2
    exit 2   # exit 2 = 阻断
  fi
fi

exit 0       # exit 0 = 放行

常见问题:脚本没有 #!/usr/bin/env bash 头、没有执行权限(chmod +x)、依赖 jq 但环境未安装——这三点占了新手 90% 的失败原因。可先用 claude --debug 查看钩子的实际调用与报错。

第三步:在 settings.json 中注册钩子

钩子配置写在 settings.json 的 hooks 字段下。settings.json 存在三个层级,按优先级从高到低为:企业策略 > 用户级(~/.claude/settings.json)> 项目级(.claude/settings.json)。一个事件下可挂多条钩子,按顺序执行:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh"
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/no-secrets.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/autoformat.sh" }
        ]
      }
    ]
  }
}

注意新版结构是 事件名 → [ {matcher, hooks:[{type, command}]} ]。type 目前支持 command(shell 脚本)、http(HTTP 端点)、prompt(LLM 提示)。$CLAUDE_PROJECT_DIR 是 Claude Code 注入的项目根环境变量,钩子里随时可用。

Matcher(匹配器) 决定钩子是否触发,规则因包含字符而异:纯工具名(如 Bash)精确匹配;含 | 表示多工具(Edit|Write);含正则元字符则按正则解析,例如 mcp__.*__write.* 能匹配所有 MCP 服务器下名字含 write 的工具,mcp__memory__.* 匹配 memory 服务器的全部工具。留空 matcher 的钩子对该事件每次都触发。

预期结果:配置写入后重启会话,对一个 Write 操作应能看到对应 PostToolUse 钩子被调用(在 claude --debug 日志里)。

第四步:用 PreToolUse 决策控制精准拦截

PreToolUse 在工具执行之前触发,且无论当前权限状态如何都会运行——这意味着它是你最后一道确定性防线。用退出码只能"阻断或沉默",而用 JSON 决策可以实现更精细的控制。在 exit 0 时向 stdout 打印结构化 JSON:

#!/usr/bin/env bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')

# 规则:禁止任何 force push 与 main 分支操作
if echo "$cmd" | grep -qE 'git push .* --force|git push -f|git checkout main|git reset --hard'; then
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "禁止强制推送与对 main 的破坏性操作(由护栏钩子拦截)"
  }
}
EOF
  exit 0
fi

exit 0

permissionDecision 三种取值:

当同一事件挂了多个 PreToolUse 钩子且返回不同决策时,有明确的优先级规则(deny 通常优先于 allow),官方文档对此有完整定义,复杂场景务必查阅"PreToolUse decision control"小节确认。

常见问题:返回了 permissionDecision: "deny" 但 Edit 工具仍被执行——这通常是 JSON 格式错误(多余逗号、未转义引号)导致解析失败而回退为放行。务必用 jq -n 构造 JSON 而非手拼字符串,避免命令中的特殊字符破坏结构。

第五步:用 PostToolUse 做自动格式化与校验

PostToolUse 在工具执行之后触发,最适合副作用类工作:自动 lint、格式化、运行类型检查、写审计日志。它不能撤销已发生的操作,但可以在文件落盘后立即修正。

一个典型的"写完即格式化"钩子:

#!/usr/bin/env bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

# 仅处理项目内的源码文件
case "$file_path" in
  *.js|*.ts|*.jsx|*.tsx)
    # 自动用项目配置格式化
    npx prettier --write "$file_path" >/dev/null 2>&1
    # 运行类型检查,失败则反馈给 Claude
    if ! npx tsc --noEmit "$file_path" >/dev/null 2>&1; then
      echo "类型检查未通过,请修复 $file_path" >&2
    fi
    ;;
  *.py)
    ruff format "$file_path" >/dev/null 2>&1
    ruff check --fix "$file_path" >/dev/null 2>&1
    ;;
esac
exit 0

PostToolUse 同样支持 JSON 决策控制(PostToolUseFailure 处理工具调用失败的情况),并能在 hookSpecificOutput 里返回额外上下文。由于它在每次工具调用后都跑,务必让脚本快速返回(毫秒级),否则会拖慢整个 Agent 循环。重活(全量测试套件)留给 Stop 钩子。

第六步:用 UserPromptSubmit 注入上下文

UserPromptSubmit 在用户按下回车、Claude 看到提问之前触发。它最强大的用法是为每条提问附加额外上下文,无需用户手动维护。这比把所有背景塞进 CLAUDE.md 更动态——可以根据当前 git 分支、时间、文件状态实时生成。

#!/usr/bin/env bash
branch=$(git branch --show-current 2>/dev/null)
uncommitted=$(git status --porcelain 2>/dev/null | wc -l)
today=$(date +%Y-%m-%d)

# exit 0 + additionalContext 注入到提问上下文
cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "[自动注入] 当前分支: ${branch} | 未提交变更: ${uncommitted} 个文件 | 日期: ${today}。回答请考虑当前分支上下文。"
  }
}
EOF
exit 0

注意:UserPromptSubmit 用 exit 0 + JSON 的 additionalContext 来补充上下文;若想直接拦截/拒绝某条提问,则用 exit 2(stderr 反馈给用户,提问不会送达 Claude)。这能实现"工作时间外禁止提问"或"提问必须包含任务编号"等软约束。

第七步:用 Notification 做桌面提醒

当 Claude 需要你确认权限、或长时间空闲时,它会触发 Notification 事件。配置一个钩子把通知转发到桌面/手机,你就无需一直盯着终端。macOS 示例:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          { "type": "command", "command": "osascript -e 'display notification \"Claude 需要确认权限\" with title \"Claude Code\" sound name \"Glass\"'" }
        ]
      }
    ]
  }
}

matcher 在 Notification 事件上按通知类型过滤(如 permission_prompt、idle_prompt)。Notification 钩子是纯副作用,不能阻断或修改通知本身。在 Linux 上可换成 notify-send,或转发到 Slack/钉钉 webhook 实现移动办公。


实战案例:构建一个完整的安全护栏系统

让我们把上述能力组装成一个真实可用的护栏体系。目标项目是一个 Web 服务,Claude Code 负责日常开发,但必须满足:

  1. 绝不执行 rm -rf 根目录、git push --force 等破坏性命令
  2. 绝不写入 含密钥/令牌的文件(防泄密提交)
  3. 每次写文件自动格式化
  4. 每轮提问自动注入当前分支与变更数
  5. Claude 等待确认时桌面通知

项目目录结构:

my-service/
├── .claude/
│   ├── settings.json
│   └── hooks/
│       ├── guard-bash.sh
│       ├── no-secrets.sh
│       ├── autoformat.sh
│       ├── inject-context.sh
│       └── notify.sh
├── CLAUDE.md
└── src/

.claude/settings.json(项目级配置,随仓库分发,团队共享护栏):

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh" }] },
      { "matcher": "Write|Edit|MultiEdit", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/no-secrets.sh" }] }
    ],
    "PostToolUse": [
      { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/autoformat.sh" }] }
    ],
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/inject-context.sh" }] }
    ],
    "Notification": [
      { "matcher": "permission_prompt", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.sh" }] }
    ]
  }
}

no-secrets.sh(防止密钥落盘——PreToolUse 在写入前拦截):

#!/usr/bin/env bash
set -euo pipefail
input=$(cat)
content=$(echo "$input" | jq -r '.tool_input.content // .tool_input.new_string // ""')

# 常见密钥模式:AWS、OpenAI、私钥头
if echo "$content" | grep -qiE 'AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{20,}|-----BEGIN (RSA |EC )?PRIVATE KEY-----'; then
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "检测到疑似密钥/私钥,已拦截写入。请改用环境变量或密钥管理服务。"
  }
}
EOF
  exit 0
fi
exit 0

测试流程:在一个交互会话里,让 Claude "把 AWS 密钥 AKIA... 写进 config.py"——你会看到 deny 反馈,文件不会被创建。再让它 "执行 rm -rf node_modules /"——guard-bash 钩子拦截。而正常写代码会被 PostToolUse 自动 prettier 格式化。每条提问顶部的注入上下文让你随时知道当前分支状态。需要确认时桌面弹窗响起。

整个护栏系统的请求-拦截-反馈闭环如下:

sequenceDiagram
    participant U as 用户
    participant CC as Claude Code
    participant H as 钩子脚本
    participant T as 工具(Bash/Edit)

    U->>CC: "把密钥写进 config.py"
    CC->>H: PreToolUse(Edit) JSON via stdin
    H->>H: 正则匹配到 AKIA 密钥
    H-->>CC: exit0 + permissionDecision: deny
    CC-->>CC: 拦截,将 reason 反馈给模型
    CC-->>U: "已拦截:检测到密钥,建议用环境变量"
    Note over U,T: 危险写入从未发生

    U->>CC: "给 utils.ts 加个工具函数"
    CC->>H: PreToolUse(Edit) 检查通过
    H-->>CC: exit0 放行
    CC->>T: 执行 Edit 写入
    T-->>CC: 完成
    CC->>H: PostToolUse(Write)
    H->>H: prettier --write 格式化
    H-->>CC: exit0
    CC-->>U: 已完成并自动格式化

这就是一个"自动批准但有安全网"的工作流:权限策略可以适度放宽,因为钩子守门员会兜住真正的红线,既保持流畅又守住底线。


进阶:异步钩子、HTTP 钩子与超时控制

随着规模扩大,几个进阶点值得关注:

一个值得养成的工程习惯:用 claude --debug 启动会话调试钩子,它会把每个钩子的 stdin/stdout/exit code 完整打印出来,是排查"为什么我的钩子没生效/被忽略"的利器。


延伸阅读

训练营适用性评估

维度 评估
适合的训练营类型 企业级 AI 编码训练营、DevOps 自动化进阶营、安全合规导向的 AI 工程营。本主题强调"可控、可审计、防事故",契合企业落地诉求
学员基础要求 中级:需懂 shell 脚本、JSON、git,最好已完成 Claude Code 入门与权限配置章节
预计教学时长 3–4 小时(含动手实验)。建议拆为:理论 45 分钟、五个钩子逐个实现 90 分钟、完整护栏项目实战 60 分钟、调试与排错 30 分钟
实验产出 一套随仓库分发的 .claude/settings.json + 钩子脚本,可作为学员回到真实项目后直接复用的资产
风险提示 钩子是确定性拦截,但规则靠正则,存在误报/漏报。教学时应强调:护栏是"深度防御"的一层,不替代 code review 与最小权限原则;禁止依赖单一钩子保障生产安全

参考来源

  1. Hooks reference - Claude Code Docs — 官方权威参考:事件清单、JSON schema、退出码与决策控制语义
  2. Automate actions with hooks - Claude Code Docs — 官方快速上手指南与示例
  3. Claude Code Hooks: Complete Guide with 20+ Ready-to-Use Examples (2026) — 20+ 可复用的格式化、审计、护栏配置
  4. Building Guardrails for AI Coding Assistants: A PreToolUse Hook System — PreToolUse 拦截系统的深度设计实践
  5. Claude Code: Auto-Approve Tools While Keeping a Safety Net with Hooks — "自动批准+安全网"模式的工程化方案

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入