【学习笔记】Claude Code Hooks 详解

13 min

学习笔记系列 · 第四篇

本篇深入讲解 Claude Code Hooks:生命周期事件、hooks.json 配置、hook 类型(command/http/prompt/agent)、输入输出格式和实战模式。


1. Hook 是什么

Hook 是在 Claude Code 生命周期特定节点自动执行的脚本或动作。它提供确定性控制——确保某些操作始终发生,而不是依赖 LLM 来决定是否执行。

用户提交 prompt

UserPromptSubmit hook → 注入额外上下文

Claude 处理

Claude 调用 Edit 工具

PreToolUse hook → 检查是否允许编辑该文件

Edit 执行

PostToolUse hook → 自动格式化编辑后的文件

Claude 完成回复

Stop hook → 检查是否遗漏了什么

Hook 的三个特点:

  1. 确定性:不依赖 LLM 判断,事件触发就执行
  2. 可拦截PreToolUse 可以阻止工具调用
  3. 可注入:可以在 prompt 提交时注入额外上下文

2. 生命周期事件全景

会话级(每会话一次)

事件何时触发Matcher 过滤对象
SessionStart会话开始或恢复startupresumeclearcompact
Setup--init-only--init--maintenance 启动时initmaintenance
SessionEnd会话终止clearresumelogoutother

Turn 级(每轮一次)

事件何时触发Matcher 过滤对象
UserPromptSubmit用户提交 prompt,Claude 处理前(无 matcher)
UserPromptExpansion用户输入的命令展开为 prompt 前展开命令名
StopClaude 完成回复时(无 matcher)
StopFailureturn 因 API 错误结束时(无 matcher)

工具级(每个工具调用)

事件何时触发Matcher 过滤对象
PreToolUse工具执行前,可阻止工具名:BashEdit|Writemcp__.*
PermissionRequest权限对话框出现时工具名
PermissionDenied工具调用被自动模式分类器拒绝时工具名
PostToolUse工具执行成功后工具名
PostToolUseFailure工具执行失败后工具名
PostToolBatch一批并行工具调用全部完成后(无 matcher)

其他事件

事件何时触发Matcher 过滤对象
NotificationClaude Code 发送通知时通知类型
MessageDisplay助手消息显示时(无 matcher)
SubagentStartsubagent 被创建时agent 类型
SubagentStopsubagent 完成时agent 类型
TaskCreatedTaskCreate 被调用时(无 matcher)
TaskCompleted任务标记完成时(无 matcher)
InstructionsLoadedCLAUDE.md 或 rules 文件加载时(无 matcher)
ConfigChange配置文件变更时(无 matcher)
FileChanged监视的文件变更时文件路径
PreCompact上下文压缩前(无 matcher)
PostCompact上下文压缩后(无 matcher)
ElicitationMCP server 请求用户输入时(无 matcher)
TeammateIdle团队 agent 即将空闲时(无 matcher)

3. hooks.json 配置格式

基本结构

{
  "hooks": {
    "EventName": [
      {
        "matcher": "可选的匹配模式",
        "hooks": [
          {
            "type": "command",
            "command": "your-script.sh"
          }
        ]
      }
    ]
  }
}

多个 matcher 的示例

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"Bash command executed\" >> /tmp/claude-commands.log"
          }
        ]
      }
    ]
  }
}

多个 hook 的示例(同一 matcher)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "$HOME/scripts/check-protected-files.sh"
          },
          {
            "type": "prompt",
            "prompt": "Verify that the file being written follows project conventions."
          }
        ]
      }
    ]
  }
}

4. Hook 类型

4.1 command 类型(最常用)

执行 shell 命令。输入通过 stdin 传入 JSON,通过 exit code 控制行为。

{
  "type": "command",
  "command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
  "args": [],
  "timeout": 30
}
字段必需说明
type"command"
command要执行的 shell 命令
args命令参数数组;设置后使用 exec form,不经 shell 解释,适合传 ${CLAUDE_PLUGIN_ROOT} 这类路径
timeout超时秒数。command/http/mcp_tool 多数事件默认 600 秒;UserPromptSubmit 默认 30 秒,MessageDisplay 默认 10 秒
async / asyncRewake让 hook 后台运行;asyncRewake 可在特定失败时唤醒 Claude
shellbashpowershell;设置 args 时忽略

4.2 http 类型

向 HTTP 端点发送 POST 请求。

{
  "type": "http",
  "url": "https://my-service.com/hooks/validate",
  "headers": {
    "Authorization": "Bearer ${API_TOKEN}"
  },
  "timeout": 10
}

4.3 prompt 类型

让 LLM 评估 prompt 并做出决策。适合需要语义理解的场景。

{
  "type": "prompt",
  "prompt": "Analyze the tool call and determine if it follows project conventions. Block any destructive operations."
}

prompt 类型适合:

  • 检查代码编辑是否符合项目规范
  • 评估 Bash 命令的安全性
  • 验证文件路径是否合规

4.4 mcp_tool 类型

调用 MCP server 上的工具。

{
  "type": "mcp_tool",
  "server": "my-mcp-server",
  "tool": "validate-code"
}

4.5 agent 类型

启动一个 agent 来处理 hook 事件。

{
  "type": "agent",
  "prompt": "Review the code changes and report any issues."
}

5. Matcher 过滤

Matcher 决定 hook 何时触发。不设置 matcher 等同于匹配所有。

matcher 语法

"Edit"              → 精确匹配 Edit 工具
"Edit|Write"        → 匹配 Edit 或 Write(管道分隔)
"Bash"              → 匹配所有 Bash 调用
"mcp__.*"           → 正则匹配所有 MCP 工具
省略 matcher 或 "*"  → 匹配所有支持 matcher 的事件

各事件的 matcher 值

事件matcher 过滤对象示例值
PreToolUse / PostToolUse / PostToolUseFailure工具名BashEdit|Writemcp__.*
PermissionRequest / PermissionDenied工具名同上
SessionStart启动方式startupresumeclearcompact
SetupCLI 标志initmaintenance
SessionEnd结束原因clearresumelogoutother
Notification通知类型permission_promptidle_promptauth_success
SubagentStart / SubagentStopagent 类型general-purposeExplore、自定义名
InstructionsLoaded加载原因session_startpath_glob_matchnested_traversal
FileChanged文件路径*.tssrc/**

UserPromptSubmitPostToolBatchStopTaskCreatedTaskCompletedWorktreeCreateWorktreeRemoveMessageDisplayCwdChanged 等事件不支持 matcher;即使写了也会被忽略。PreCompact / PostCompact 当前也不是按 automanual matcher 过滤。


6. if 条件过滤

if 字段使用权限规则语法进行更精细的过滤,只在 PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied 事件上有效。

{
  "matcher": "Bash",
  "if": "Bash(rm *)",
  "hooks": [
    {
      "type": "command",
      "command": "echo 'Dangerous rm command blocked' >&2 && exit 2"
    }
  ]
}
{
  "matcher": "Edit",
  "if": "Edit(*.prod.*)",
  "hooks": [
    {
      "type": "prompt",
      "prompt": "This is a production config file. Verify the edit is safe."
    }
  ]
}

注意:设置了 if 的事件如果不是上面列出的工具事件,hook 将永远不会运行。


7. 输入输出格式

7.1 输入(stdin JSON)

所有 hook 通过 stdin 接收 JSON 数据。通用字段:

{
  "session_id": "abc-123",
  "cwd": "/path/to/project",
  "event": "PreToolUse"
}

工具事件额外字段:

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/path/to/file.ts",
    "old_string": "old",
    "new_string": "new"
  }
}

7.2 输出(stdout JSON)

通过 stdout 输出 JSON 控制行为:

PreToolUse 输出

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Cannot edit production config files."
  }
}

permissionDecision 值:

效果
allow允许执行
deny阻止执行
ask询问用户
defer不做决定,继续走正常权限流程

Hook 要么用 exit code,要么用 exit 0 + stdout JSON。如果 exit code 是 2,stdout JSON 会被忽略。

顶层 decision 输出

UserPromptSubmitUserPromptExpansionPostToolUsePostToolUseFailurePostToolBatchStopSubagentStopConfigChangePreCompact 等事件可以用顶层 decision: "block" 阻止后续处理:

{
  "decision": "block",
  "reason": "This task is incomplete; tests were not run."
}

注入额外上下文

支持上下文注入的事件可以通过 hookSpecificOutput.additionalContext 注入信息给 Claude:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Project uses TypeScript strict mode. All new files must include type annotations."
  }
}

7.3 Exit Code

Exit Code含义
0成功,无特殊行为
2对支持阻止的事件表示阻止/反馈;具体效果取决于事件

不同事件对 exit code 2 的处理不同。比如 PreToolUse 会阻止工具调用,UserPromptSubmit 会阻止 prompt 继续处理,PostToolUse 只能把 stderr 反馈给 Claude,因为工具已经执行完成。需要精细控制时优先使用 exit 0 + JSON


8. 配置位置与作用域

位置范围可共享
~/.claude/settings.json所有项目否,仅本机
.claude/settings.json当前项目是,可提交到 repo
.claude/settings.local.json当前项目否,gitignored
Managed policy settings组织范围是,管理员控制
插件 hooks/hooks.json插件启用时是,随插件分发
Skill/Agent frontmatter组件激活期间是,定义在组件文件中

企业管理

管理员可以用 allowManagedHooksOnly 阻止用户、项目和插件 hook,只允许托管 hook 运行。但 enabledPlugins 中强制启用的插件 hook 豁免此限制。


9. 插件中的 Hooks

hooks.json 格式

插件 hook 放在 hooks/hooks.json,需要额外的 hooks 包裹层:

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

内联在 plugin.json 中

也可以把 hook 直接写在 plugin.json 里:

{
  "name": "my-plugin",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh"
          }
        ]
      }
    ]
  }
}

Skill/Agent 内联 Hook

Hook 也可以直接写在 skill 或 agent 的 frontmatter 中,只在组件激活期间生效:

---
name: deploy
description: Deploy to production.
disable-model-invocation: true
hooks:
  PostToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "${CLAUDE_SKILL_DIR}/scripts/log-deploy.sh"
---

## Deploy Steps

1. Run the build command
2. Deploy to server

10. 实战模式

10.1 自动格式化

每次编辑文件后自动运行 Prettier:

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

10.2 保护文件

阻止编辑生产配置文件:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

protect-files.sh:

#!/bin/bash
FILE=$(jq -r '.tool_input.file_path')
case "$FILE" in
  *.production.*|*.prod.*)
    echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Cannot edit production config files."}}'
    # 也可以直接把原因写到 stderr 并 exit 2;这里用 JSON 做结构化控制。
    exit 0
    ;;
esac
exit 0

10.3 会话启动时注入上下文

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "cat \"${CLAUDE_PROJECT_DIR}/.claude/context/project-rules.md\""
          }
        ]
      }
    ]
  }
}

10.4 通知提醒

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

10.5 Lint 检查

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Check if the edited file has any obvious syntax errors or missing imports. If it does, report them."
          }
        ]
      }
    ]
  }
}

10.6 完成检查

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Verify that the task is fully complete. Check: 1) All requested changes are made. 2) No TODOs left. 3) Tests would pass. If incomplete, describe what's missing."
          }
        ]
      }
    ]
  }
}

11. 调试技巧

11.1 查看已注册的 Hook

/hooks

11.2 Debug 模式

claude --debug

11.3 直接测试脚本

# 模拟 PreToolUse 事件
echo '{"tool_name":"Edit","tool_input":{"file_path":"test.ts"}}' | bash hooks/protect-files.sh

11.4 常见问题排查

  • Hook 没触发?检查事件名大小写(PostToolUse 不是 postToolUse
  • Matcher 不匹配?确认 pattern 是否和工具名精确一致
  • 编辑 hooks.json 后没生效?重启 Claude Code
  • if 条件不生效?确认是否用在支持 if 的工具事件上

12. 本项目实例

GLM StatusLine 插件目前没有使用 hooks。插件的核心功能通过 skill + bin 脚本实现,不需要自动触发逻辑。

如果将来需要扩展,可以添加:

{
  "description": "GLM StatusLine lifecycle hooks",
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/bin/glm-statusline-install.js check",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

这个 hook 会在每次会话启动时检查状态栏配置是否仍然有效。


参考资料


系列导航

← 上一篇下一篇 →
第三篇:Agents第五篇:MCP

📌 本文相关仓库BingqiangZhou/CCStatusline — Claude Code 插件系统学习笔记 & GLM StatusLine 实践案例