跳过正文
Claude Code 教程系列:钩子(Hooks)
  1. Posts/

Claude Code 教程系列:钩子(Hooks)

·3301 字·7 分钟

钩子是响应Claude Code会话中特定事件而自动执行的脚本。它们实现自动化、验证、权限管理和自定义工作流。

核心概念
#

什么是钩子?
#

钩子是在Claude Code中发生特定事件时自动执行的自动化操作(shell命令、HTTP webhooks、LLM提示或子代理评估)。它们通过JSON输入接收信息,并通过退出代码和JSON输出传达结果。

关键特性:

  • 事件驱动的自动化
  • 基于JSON的输入/输出
  • 支持command、prompt、http和agent钩子类型
  • 工具特定的模式匹配

配置位置
#

钩子在具有特定结构的设置文件中配置:

  • ~/.claude/settings.json - 用户设置(所有项目)
  • .claude/settings.json - 项目设置(可共享,已提交)
  • .claude/settings.local.json - 本地项目设置(未提交)
  • Managed policy - 组织范围设置
  • Plugin hooks/hooks.json - 插件范围钩子
  • Skill/Agent frontmatter - 组件生命周期钩子

钩子系统架构

图:钩子系统的多层次配置架构,从全局配置到组件级钩子。

基本配置结构
#

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

模式匹配
#

模式描述示例
精确字符串匹配特定工具"Write"
正则表达式匹配多个工具"Edit|Write"
通配符匹配所有工具"*"""
MCP工具服务器和工具模式"mcp__memory__.*"

钩子类型
#

Claude Code支持四种钩子类型:

Command Hooks
#

默认钩子类型。执行shell命令并通过JSON stdin/stdout和退出代码进行通信。

{
  "type": "command",
  "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
  "timeout": 60
}

HTTP Hooks
#

接收与command hooks相同JSON输入的远程webhook端点。HTTP hooks将JSON POST到URL并接收JSON响应。

{
  "hooks": {
    "PostToolUse": [{
      "type": "http",
      "url": "https://my-webhook.example.com/hook",
      "matcher": "Write"
    }]
  }
}

Prompt Hooks
#

LLM评估的提示,其中钩子内容是Claude评估的提示。主要用于StopSubagentStop事件,用于智能任务完成检查。

{
  "type": "prompt",
  "prompt": "评估Claude是否完成了所有请求的任务。",
  "timeout": 30
}

LLM评估提示并返回结构化决策。

Agent Hooks
#

基于子代理的验证钩子,生成专用代理来评估条件或执行复杂检查。与prompt hooks(单轮LLM评估)不同,agent hooks可以使用工具并执行多步推理。

{
  "type": "agent",
  "prompt": "验证代码更改遵循我们的架构指南。检查相关设计文档并进行比较。",
  "timeout": 120
}

钩子事件
#

Claude Code支持26个钩子事件

事件触发时机可阻塞常见用途
SessionStart会话开始/恢复/清除/压缩环境设置
InstructionsLoadedCLAUDE.md或规则文件加载后修改/过滤指令
UserPromptSubmit用户提交提示时验证提示
PreToolUse工具执行前是 (允许/拒绝/询问)验证、修改输入
PermissionRequest显示权限对话框时自动批准/拒绝
PermissionDenied用户拒绝权限提示时日志记录、分析、策略执行
PostToolUse工具成功后添加上下文、反馈
PostToolUseFailure工具执行失败时错误处理、日志记录
SubagentStart子代理生成时子代理设置
SubagentStop子代理完成时子代理验证
StopClaude完成响应时任务完成检查
TaskCompleted任务标记为完成时任务后操作
ConfigChange配置文件更改时是 (策略除外)对配置更新的反应
SessionEnd会话终止时清理、最终日志记录

钩子事件类型

图:主要的钩子事件类型及其关联关系,展示不同类别事件的触发时机。

退出代码
#

退出代码含义行为
0成功继续,解析JSON stdout
2阻塞错误阻止操作,stderr显示为错误
其他非阻塞错误继续,verbose模式中显示stderr

钩子执行流程
#

钩子按照以下流程在事件发生时执行:

flowchart TB
    A["事件触发"] --> B{找到匹配的钩子?}
    B -->|否| C["继续执行"]
    B -->|是| D["遍历所有匹配的钩子"]

    D --> E{钩子类型?}
    E -->|command| F["执行shell命令"]
    E -->|http| G["POST到webhook端点"]
    E -->|prompt| H["LLM评估提示"]
    E -->|agent| I["创建子代理执行"]

    F --> J{退出代码}
    G --> K{HTTP响应}
    H --> L{LLM决策}
    I --> M{子代理结果}

    J -->|0| N["解析JSON stdout"]
    J -->|2| O["阻塞操作,显示错误"]
    J -->|其他| P["继续,显示警告"]

    K -->|200| N
    K -->|其他| O

    L -->|允许| Q["继续执行"]
    L -->|阻止| O

    M -->|成功| Q
    M -->|失败| O

    N --> Q
    P --> Q
    Q --> R["处理下一个钩子<br/>或继续执行"]

    style A fill:#4dabf7,stroke:#1864ab
    style F fill:#69db7c,stroke:#2b8a3e
    style G fill:#ffd43b,stroke:#f08c00
    style H fill:#ff6b6b,stroke:#c92a2a
    style I fill:#da77f2,stroke:#862e9c
    style O fill:#e03131,stroke:#a61e4d

实用示例
#

示例1:Bash命令验证器(PreToolUse)
#

文件:.claude/hooks/validate-bash.py

#!/usr/bin/env python3
import json
import sys
import re

BLOCKED_PATTERNS = [
    (r"\brm\s+-rf\s+/", "阻止危险的rm -rf /命令"),
    (r"\bsudo\s+rm", "阻止sudo rm命令"),
]

def main():
    input_data = json.load(sys.stdin)
    
    tool_name = input_data.get("tool_name", "")
    if tool_name != "Bash":
        sys.exit(0)
    
    command = input_data.get("tool_input", {}).get("command", "")
    
    for pattern, message in BLOCKED_PATTERNS:
        if re.search(pattern, command):
            print(message, file=sys.stderr)
            sys.exit(2)  # 退出代码2 = 阻塞错误
    
    sys.exit(0)

if __name__ == "__main__":
    main()

配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
          }
        ]
      }
    ]
  }
}

示例2:安全扫描器(PostToolUse)
#

文件:.claude/hooks/security-scan.py

#!/usr/bin/env python3
import json
import sys
import re

SECRET_PATTERNS = [
    (r"password\s*=\s*['\"][^'\"]+['\"]", "潜在的硬编码密码"),
    (r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "潜在的硬编码API密钥"),
]

def main():
    input_data = json.load(sys.stdin)
    
    tool_name = input_data.get("tool_name", "")
    if tool_name not in ["Write", "Edit"]:
        sys.exit(0)
    
    tool_input = input_data.get("tool_input", {})
    content = tool_input.get("content", "") or tool_input.get("new_string", "")
    file_path = tool_input.get("file_path", "")
    
    warnings = []
    for pattern, message in SECRET_PATTERNS:
        if re.search(pattern, content, re.IGNORECASE):
            warnings.append(message)
    
    if warnings:
        output = {
            "hookSpecificOutput": {
                "hookEventName": "PostToolUse",
                "additionalContext": f"{file_path}的安全警告:" + "; ".join(warnings)
            }
        }
        print(json.dumps(output))
    
    sys.exit(0)

if __name__ == "__main__":
    main()

示例3:自动格式化代码(PostToolUse)
#

文件:.claude/hooks/format-code.sh

#!/bin/bash

# 从stdin读取JSON
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")

if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
    exit 0
fi

# 根据文件扩展名格式化
case "$FILE_PATH" in
    *.js|*.jsx|*.ts|*.tsx|*.json)
        command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
        ;;
    *.py)
        command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null
        ;;
    *.go)
        command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null
        ;;
esac

exit 0

示例4:提示验证器(UserPromptSubmit)
#

文件:.claude/hooks/validate-prompt.py

#!/usr/bin/env python3
import json
import sys
import re

BLOCKED_PATTERNS = [
    (r"delete\s+(all\s+)?database", "危险:数据库删除"),
    (r"rm\s+-rf\s+/", "危险:根目录删除"),
]

def main():
    input_data = json.load(sys.stdin)
    prompt = input_data.get("user_prompt", "") or input_data.get("prompt", "")
    
    for pattern, message in BLOCKED_PATTERNS:
        if re.search(pattern, prompt, re.IGNORECASE):
            output = {
                "decision": "block",
                "reason": f"已阻止:{message}"
            }
            print(json.dumps(output))
            sys.exit(0)
    
    sys.exit(0)

if __name__ == "__main__":
    main()

示例5:智能停止钩子(基于Prompt)
#

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "审查Claude是否完成了所有请求的任务。检查:1) 所有文件是否已创建/修改?2) 是否有未解决的错误?如果不完整,说明缺少什么。",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

环境变量
#

变量可用性描述
CLAUDE_PROJECT_DIR所有钩子项目根目录的绝对路径
CLAUDE_ENV_FILESessionStart, CwdChanged, FileChanged持久化环境变量的文件路径
CLAUDE_CODE_REMOTE所有钩子如果在远程环境中运行则为"true"
${CLAUDE_PLUGIN_ROOT}插件钩子插件目录的路径
${CLAUDE_PLUGIN_DATA}插件钩子插件数据目录的路径

组件级钩子
#

钩子可以附加到特定组件(skills、agents、commands)的frontmatter中:

在SKILL.md、agent.md或command.md中:

---
name: secure-operations
description: 执行带有安全检查的操作
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/check.sh"
          once: true  # 每会话仅运行一次
---

组件钩子支持的事件:PreToolUsePostToolUseStop

最佳实践
#

安全考虑
#

Do’s ✅
#

  • 验证并清理所有输入
  • 引用shell变量:"$VAR"
  • 阻止路径遍历(..
  • 使用$CLAUDE_PROJECT_DIR的绝对路径
  • 跳过敏感文件(.env.git/、密钥)
  • 首先在隔离环境中测试钩子
  • 对HTTP hooks使用显式allowedEnvVars

Don’ts ❌
#

  • 不要盲目信任输入数据
  • 不要使用未引用的:$VAR
  • 不要允许任意路径
  • 不要硬编码路径
  • 不要处理所有文件
  • 不要部署未经测试的钩子
  • 不要将所有env变量暴露给webhooks

调试
#

启用调试模式
#

使用debug标志运行Claude以获取详细的钩子日志:

claude --debug

详细模式
#

在Claude Code中使用Ctrl+O启用详细模式并查看钩子执行进度。

独立测试钩子
#

# 使用示例JSON输入测试
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py

# 检查退出代码
echo $?

相关资源
#


这是Claude Code 教程系列的第六篇文章。下一篇文章将介绍Claude Code的插件系统。

相关文章

Claude Code 教程系列:内存系统(Memory)

·8139 字·17 分钟
内存系统使Claude能够在多个会话和对话中保持持久化的上下文。与临时上下文窗口不同,内存文件允许你在团队间共享项目标准、存储个人开发偏好、维护特定目录的规则,并将外部文档导入为版本控制的一部分。