Claude Code 的 Hooks 系统允许你在特定事件发生时自动执行脚本。比如在写入文件后自动格式化、在执行命令前做安全检查、在会话结束时发送通知。本文通过 7 个完整的实战案例,带你掌握 Hooks 的高级用法。

一、Hooks 基础概念

1.1 四种 Hook 类型

Hook 类型 触发时机 典型用途
PreToolUse 工具调用之前 安全检查、权限拦截
PostToolUse 工具调用之后 自动格式化、日志记录
Notification 需要通知用户时 发送消息到 Slack/钉钉
Stop Claude Code 完成回复时 自动测试、自动 lint

1.2 配置位置

Hooks 在 .claude/settings.json 中配置:

{
  "hooks": {
    "PreToolUse": [...],
    "PostToolUse": [...],
    "Notification": [...],
    "Stop": [...]
  }
}

1.3 Hook 配置结构

{
  "matcher": "toolName 的匹配规则(正则或字符串)",
  "hooks": [
    {
      "type": "command",
      "command": "要执行的 shell 命令"
    }
  ]
}

Hook 脚本通过 stdin 接收 JSON 格式的事件数据,可以据此做出判断。

二、实战案例

案例 1:文件写入后自动格式化(PostToolUse)

每次 Claude Code 写入或编辑文件后,自动运行 Prettier 格式化。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if [ -n \"$filepath\" ] && echo \"$filepath\" | grep -qE '\\.(ts|tsx|js|jsx|vue|css|json)$'; then npx prettier --write \"$filepath\" 2>/dev/null; fi"
          }
        ]
      }
    ]
  }
}
这个 Hook 只对前端相关文件(.ts/.tsx/.js/.jsx/.vue/.css/.json)执行格式化,避免对其他文件产生副作用。

案例 2:危险命令拦截(PreToolUse)

阻止 Claude Code 执行危险的 shell 命令,如 rm -rfDROP TABLE 等。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "cmd=$(echo $CLAUDE_TOOL_INPUT | jq -r '.command') && if echo \"$cmd\" | grep -qiE 'rm\\s+-rf\\s+/|drop\\s+table|drop\\s+database|truncate\\s+table|:(){ :|:& };:'; then echo 'BLOCKED: 检测到危险命令,已拦截' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}

当 Hook 脚本以退出码 2 退出时,Claude Code 会阻止该工具调用。退出码含义:

退出码 含义
0 允许执行,不修改
1 Hook 执行出错(不阻止工具调用)
2 阻止工具调用

案例 3:敏感文件保护(PreToolUse)

阻止 Claude Code 读取或修改包含密钥的文件。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if echo \"$filepath\" | grep -qiE '\\.env$|\\.env\\.local$|credentials|secrets|private.key|id_rsa'; then echo 'BLOCKED: 该文件可能包含敏感信息' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}

案例 4:钉钉通知(Notification)

当 Claude Code 发出通知(如等待用户确认、任务完成)时,自动推送到钉钉群。

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "msg=$(echo $CLAUDE_NOTIFICATION | jq -r '.message // \"Claude Code 通知\"') && curl -s -X POST 'https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN' -H 'Content-Type: application/json' -d \"{\\\"msgtype\\\": \\\"text\\\", \\\"text\\\": {\\\"content\\\": \\\"[Claude Code] $msg\\\"}}\""
          }
        ]
      }
    ]
  }
}

案例 5:Slack 通知(Notification)

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "msg=$(echo $CLAUDE_NOTIFICATION | jq -r '.message // \"Claude Code notification\"') && curl -s -X POST 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL' -H 'Content-Type: application/json' -d \"{\\\"text\\\": \\\"$msg\\\"}\""
          }
        ]
      }
    ]
  }
}

案例 6:Stop 后自动运行测试(Stop)

每当 Claude Code 完成一轮回复,自动运行相关的单元测试。

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "changed=$(git diff --name-only HEAD 2>/dev/null | grep -E '\\.(ts|js)$' | head -5) && if [ -n \"$changed\" ]; then for f in $changed; do testfile=$(echo $f | sed 's/\\.ts$/.spec.ts/' | sed 's/\\.js$/.spec.js/'); if [ -f \"$testfile\" ]; then npx jest \"$testfile\" --passWithNoTests 2>&1 | tail -5; fi; done; fi"
          }
        ]
      }
    ]
  }
}
该 Hook 会检测 Git 变更中的 .ts/.js 文件,查找对应的 .spec.ts/.spec.js 测试文件并运行。只在有变更时触发,不会浪费时间。

案例 7:ESLint 自动修复(PostToolUse)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if [ -n \"$filepath\" ] && echo \"$filepath\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx eslint --fix \"$filepath\" 2>/dev/null || true; fi"
          }
        ]
      }
    ]
  }
}

三、完整配置示例

将上述多个 Hook 组合成完整的 .claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "cmd=$(echo $CLAUDE_TOOL_INPUT | jq -r '.command') && if echo \"$cmd\" | grep -qiE 'rm\\s+-rf\\s+/|drop\\s+table|drop\\s+database'; then echo 'BLOCKED' >&2; exit 2; fi"
          }
        ]
      },
      {
        "matcher": "Read|Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if echo \"$filepath\" | grep -qiE '\\.env$|credentials|secrets'; then echo 'BLOCKED' >&2; exit 2; fi"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if [ -n \"$filepath\" ] && echo \"$filepath\" | grep -qE '\\.(ts|tsx|js|jsx|vue|css|json)$'; then npx prettier --write \"$filepath\" 2>/dev/null; fi"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "msg=$(echo $CLAUDE_NOTIFICATION | jq -r '.message') && curl -s -X POST 'https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN' -H 'Content-Type: application/json' -d \"{\\\"msgtype\\\":\\\"text\\\",\\\"text\\\":{\\\"content\\\":\\\"[Claude Code] $msg\\\"}}\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "changed=$(git diff --name-only HEAD 2>/dev/null | grep -E '\\.(ts|js)$' | head -5) && if [ -n \"$changed\" ]; then npx jest --passWithNoTests 2>&1 | tail -3; fi"
          }
        ]
      }
    ]
  }
}

四、调试 Hooks

4.1 查看 Hook 输入数据

开发 Hook 时,可以先将输入数据写入日志文件:

{
  "matcher": "Write",
  "hooks": [
    {
      "type": "command",
      "command": "echo \"$(date): TOOL_INPUT=$CLAUDE_TOOL_INPUT\" >> /tmp/claude-hooks.log"
    }
  ]
}

4.2 常见问题

  • Hook 不触发:检查 matcher 是否正确匹配工具名称(区分大小写)
  • 命令执行失败:确保命令中的路径是绝对路径或在 PATH 中
  • jq 未安装:Hook 脚本依赖 jq 解析 JSON,需要提前安装
  • 退出码误用:exit 2 会阻止工具调用,exit 1 只是报错但不阻止

五、最佳实践

  1. Hook 脚本要快:Hook 是同步执行的,耗时长的操作会阻塞 Claude Code
  2. 做好错误处理:Hook 失败不应该影响正常工作流,用 || true 兜底
  3. 按需启用:不要配置过多 Hook,每个 Hook 都有执行开销
  4. 团队共享:将 .claude/settings.json 纳入版本控制,团队统一配置
  5. 渐进式添加:先从最有价值的 Hook(如自动格式化)开始,逐步扩展

总结

Hooks 是 Claude Code 自动化工作流的核心机制。通过合理配置 PreToolUse(安全守卫)、PostToolUse(自动格式化)、Notification(即时通知)和 Stop(自动测试)四类 Hook,你可以打造一个安全、高效、自动化的 AI 编程环境。

接口配置参考:https://9m8m.com/docs/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐