Hooks

Hooks

在 Claude 使用工具前后自动运行的确定性脚本。

Hooks vs. CLAUDE.md

关键区别:

  • CLAUDE.md — 建议性。Claude 会阅读并_尝试_遵循,但可能遗忘或判断失误。
  • Hooks — 确定性。你的脚本每次都会自动运行,无论如何。Claude 无法跳过。

使用 CLAUDE.md 来设置指南和偏好。使用 hooks 来设置绝对不能违反的规则。

Hook 事件类型

Hooks 在 Claude 代理循环的特定节点触发。最常用的事件:

事件触发时机
PreToolUse工具执行前——可批准、拒绝或修改工具调用
PostToolUse工具成功完成后
PostToolUseFailure工具执行失败后
UserPromptSubmit用户提交提示词后、Claude 处理前
Stop主 Agent 完成响应时
SubagentStart / SubagentStop子 Agent 启动或完成时
TaskCreated / TaskCompletedAgent team 任务创建或完成时
TeammateIdleAgent team teammate 进入 idle 状态时
SessionStart / SessionEnd会话开始或结束时
PreCompact / PostCompact上下文压缩前后
Notification权限提示、空闲提醒、认证事件
PermissionDenied自动模式分类器拒绝后(返回 {retry: true} 可让 Claude 重试)

配置

Hooks 在 .claude/settings.jsonhooks 键下定义。每个事件映射到一组 matcher 对象,每个 matcher 对象包含一个 hooks 数组:

.claude/settings.json
{
"hooks": {
  "PostToolUse": [
    {
      "matcher": "Write",
      "hooks": [
        {
          "type": "command",
          "command": "jq -r '.tool_input.file_path' | xargs npx eslint --fix"
        }
      ]
    }
  ]
}
}

matcher 是对工具名称匹配的正则(大小写敏感),用 | 匹配多个工具:"Write|Edit"type 字段决定 handler 类型:"command" 运行 shell 脚本,"http" 把事件 JSON POST 到端点,"mcp_tool" 调用已连接的 MCP 工具,"prompt" 让 Claude 评估,"agent" 启动 subagent。

快速确定性检查优先用 command hooks。只有检查真的需要模型判断时,才用 prompt 或 agent hooks;它们会增加延迟和 token 成本。

实用示例

每次写入后自动 Lint

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx eslint --fix"
          }
        ]
      }
    ]
  }
}

阻止写入敏感文件

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | grep -qE '(\\.env|secrets|credentials)' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}

如果 hook 返回退出码 2,Claude Code 会阻止工具执行并将错误消息反馈给 Claude。退出码 0 表示成功;其他非零退出码是非阻塞错误。

修改后运行测试

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "file=$(jq -r '.tool_input.file_path'); if echo \"$file\" | grep -q 'src/'; then npm test -- --related \"$file\"; fi"
          }
        ]
      }
    ]
  }
}

Claude 完成时运行脚本

json
{
  "hooks": {
    "Stop": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude 完成了\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Hook 输入与环境变量

Hook 脚本通过 stdin 以 JSON 形式接收完整事件上下文 —— 用 jq 等工具解析。关键字段:

  • tool_name — 被调用的工具(如 WriteBashRead
  • tool_input — 工具参数对象;对于 Write/Edit/Read,tool_input.file_path 始终是绝对路径
  • tool_response — (仅 PostToolUse)工具返回的结果
  • session_idcwdtranscript_path

Hook 命令实际能拿到的环境变量:

  • $CLAUDE_PROJECT_DIR — 项目根目录
  • $CLAUDE_PLUGIN_ROOT / $CLAUDE_PLUGIN_DATA — 插件定义的 hook 中才会设置
  • $CLAUDE_ENV_FILE — 仅在 SessionStart / Setup / CwdChanged / FileChanged 事件中可用;写入 export VAR=value 行可把变量持久化到后续 Bash 调用
  • $CLAUDE_EFFORT — 当前 effort 等级(工具调用上下文中)
  • $CLAUDE_CODE_REMOTE — 远程 web 环境下为 "true"

文件路径和 session_id 只在 stdin JSON 里,没有对应环境变量。请用 jq -r '.tool_input.file_path',不要去找不存在的 $CLAUDE_TOOL_INPUT_FILE_PATH

使用 if 的条件 Hook

if 字段加在单个 handler 上(位于内层 hooks[] 数组里,不是外层 matcher),命中权限规则时才触发,避免不必要的进程开销:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "echo 'Git 命令被检测到' >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

禁用 Hooks

若要在某次会话中禁用所有 hooks(调试时很有用):

json
{
  "disableAllHooks": true
}

Hooks 是对 Agent 的确定性控制

建议性与确定性的区别是 Agent 设计中的核心概念。Hooks 让你获得有保障的行为,无论 LLM 做出什么决定——这是所有生产级 Agent 系统中使用的模式。

了解工具使用和 Agent 控制模式

延伸阅读

继续实践

Hooks 的核心概念已经读完

如果你想把理解变成可复用的能力,可以到 AgentWay 继续练习