Skip to content

Hooks 使用指南:自动化你的开发工作流

Hooks 是用户自定义的 shell 命令,在 Claude Code 生命周期的特定时刻自动执行。它能让某些操作必然发生,而不是依赖 LLM 去"决定"要不要做——这就是 Hooks 的核心价值:确定性控制。

用 Hooks 可以做什么?格式化代码、拦截危险命令、发桌面通知、注入上下文、记录审计日志……这些"每次都要做"的事情,Hooks 一劳永逸。

本页讲常见用法。完整事件 schema、JSON 格式、异步 hooks 等进阶内容,参见 Hooks 参考文档

5 分钟上手:创建第一个 Hook

下面以"Claude 需要你输入时发桌面通知"为例,一步步创建你的第一个 Hook。

第一步:添加配置

打开 ~/.claude/settings.json,添加 Notification hook(macOS 示例):

json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code 需要你的输入\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Linux 用 notify-send 'Claude Code' 'Claude Code 需要你的输入',Windows PowerShell 用 MessageBox API。

第二步:验证配置

在 CLI 中输入 /hooks,会看到所有已配置的 hook 事件,Notification 旁边应该有计数。

第三步:测试效果

让 Claude 做一些需要权限的操作,然后切换到其他窗口。收到通知了吗?

/hooks 菜单只读,修改 Hook 需要直接编辑 settings.json,或者告诉 Claude 帮你改。


常用自动化场景

场景一:代码编辑后自动格式化

每次 Claude 编辑文件后,自动用 Prettier 格式化,保持代码风格一致:

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

jq 从事件 JSON 里提取文件路径,传给 Prettier。macOS 用 brew install jq 安装,Debian/Ubuntu 用 apt-get install jq


场景二:保护敏感文件不被修改

防止 Claude 改动 .envpackage-lock.json.git/ 等文件:

第一步:创建保护脚本 .claude/hooks/protect-files.sh

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

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "已拦截:$FILE_PATH 匹配保护规则 '$pattern'" >&2
    exit 2
  fi
done

exit 0

第二步:赋权(macOS/Linux)

bash
chmod +x .claude/hooks/protect-files.sh

第三步:注册 Hook

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

场景三:上下文压缩后重新注入关键信息

上下文窗口满了会触发压缩(compaction),重要细节可能丢失。用 SessionStart hook 在每次压缩后重新注入关键信息:

json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo '提醒:使用 Bun 而不是 npm。提交前运行 bun test。当前迭代:认证重构。'"
          }
        ]
      }
    ]
  }
}

echo 输出的任何文本都会加入 Claude 的上下文。可以替换成动态命令,比如 git log --oneline -5 显示最近提交记录。

如果要在每次会话开始时注入上下文(不只是压缩后),用 CLAUDE.md 更合适。


场景四:记录配置变更审计日志

追踪 settings 或 skills 文件的改动,方便合规审计:

json
{
  "hooks": {
    "ConfigChange": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
          }
        ]
      }
    ]
  }
}

场景五:自动批准特定权限请求

跳过总是允许的操作的确认对话框。比如自动批准 ExitPlanMode(Claude 展示计划后的继续请求):

json
{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
          }
        ]
      }
    ]
  }
}

尽量缩小 matcher 范围。用 .* 或空 matcher 会自动批准所有权限请求,包括文件写入和 shell 命令,非常危险。


Hooks 的工作原理

所有 Hook 事件一览

事件触发时机
SessionStart会话开始或恢复时
UserPromptSubmit你提交提示词后,Claude 处理前
PreToolUse工具调用执行前(可以拦截)
PermissionRequest权限确认对话框出现时
PostToolUse工具调用成功后
PostToolUseFailure工具调用失败后
NotificationClaude Code 发送通知时
SubagentStart / SubagentStop子代理启动/结束时
StopClaude 完成回复时
StopFailureAPI 错误导致轮次结束时
TeammateIdleAgent Team 中成员即将空闲时
TaskCompleted任务被标记为完成时
InstructionsLoadedCLAUDE.md 或 rules 文件加载时
ConfigChange配置文件在会话中被修改时
WorktreeCreate / WorktreeRemoveworktree 创建/删除时
PreCompact / PostCompact上下文压缩前/后
Elicitation / ElicitationResultMCP 服务器请求用户输入时
SessionEnd会话结束时

Hook 类型

  • "type": "command" — 执行 shell 命令(最常用)
  • "type": "http" — POST 事件数据到 URL
  • "type": "prompt" — 调用 Claude 模型判断(适合需要"判断力"的场景)
  • "type": "agent" — 生成可访问文件/工具的子代理(适合需要验证代码库状态的场景)

通过退出码控制行为

退出码效果
exit 0允许操作继续
exit 2拦截操作,stderr 内容作为反馈给 Claude
其他操作继续,stderr 记录到日志

拦截示例:

bash
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q "drop table"; then
  echo "已拦截:不允许删除数据库表" >&2
  exit 2
fi

exit 0

用 Matcher 过滤触发范围

不加 matcher,hook 对每次事件都触发。加 matcher 可以缩小范围,值是正则表达式:

json
{
  "matcher": "Edit|Write"
}
事件类型Matcher 匹配的字段示例值
PreToolUse, PostToolUse, PermissionRequest工具名Bash, Edit|Write, mcp__.*
SessionStart会话启动原因startup, resume, clear, compact
SessionEnd会话结束原因clear, resume, logout
ConfigChange配置来源user_settings, project_settings
PreCompact, PostCompact压缩触发方式manual, auto

配置文件位置与作用域

位置作用范围是否可共享
~/.claude/settings.json所有项目否(仅本机)
.claude/settings.json当前项目是(可提交到仓库)
.claude/settings.local.json当前项目否(gitignore)

进阶:Prompt-based Hooks

当判断需要"智能"而非规则时,用 type: "prompt" hooks。Claude 会调用一个小模型(默认 Haiku)来做判断,返回 {"ok": true}{"ok": false, "reason": "..."}

用例:在 Claude 准备停止时检查任务是否真的完成了:

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "检查所有请求的任务是否完成。若未完成,回复 {\"ok\": false, \"reason\": \"还剩什么未完成\"}。"
          }
        ]
      }
    ]
  }
}

进阶:Agent-based Hooks

需要检查文件或运行命令来验证条件时,用 type: "agent" hooks。子代理可以读文件、搜索代码,再返回决策。

用例:在 Claude 停止前验证测试是否通过:

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "验证所有单元测试通过。运行测试套件并检查结果。$ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

常见问题排查

Hook 没有触发?

  • /hooks 确认 hook 出现在正确事件下
  • 检查 matcher 是否大小写匹配(区分大小写)
  • 非交互模式(-p)下 PermissionRequest hook 不触发,改用 PreToolUse

报错 "command not found"? 用绝对路径或 $CLAUDE_PROJECT_DIR 引用脚本;确保脚本有执行权限(chmod +x)。

Stop hook 死循环? 在 hook 脚本里检查 stop_hook_active 字段:

bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi

JSON 解析失败? Shell 配置文件(~/.zshrc)里的 echo 语句会污染 hook 输出。用 if [[ $- == *i* ]]; then 包裹,只在交互 shell 里执行。

开启详细模式(Ctrl+O)或用 claude --debug 查看 hook 执行详情。


相关文档