Hooks事件驱动自动化

1.Hooks的工作层面截然不同。它不作用于Claude的认知层,而是直接在系统执行层拦截其行为。

  • 是策略(Policy)与机制(Mechanism)的分离。

2.事件生命周期:17个事件

2.1会话级事件

  • SessionStart 在会话启动或恢复时触发。
  • SessionEnd 在会话终止时触发。
  • PreCompact 在上下文压缩前触发。

2.2 工具调用事件

  • PreToolUse(整个Hooks系统中最强大的事件)在Claude决定调用某个工具之后、工具实际执行之前触发。
  • PostToolUse 在工具成功执行后触发。
  • PostToolUseFailure 在工具执行失败后触发,主要用于错误告警以及提供纠正性反馈。
  • PermissionRequest 在权限对话框即将弹出时触发。
    通过该事件,你可以以编程方式自动批准或拒绝权限请求。
点击查看代码
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedPermissions": {}
    }
  }
}

UserPromptSubmit 在用户提交输入后、Claude开始处理之前触发。

2.3 子智能体事件

  • SubagentStart 在子智能体启动时触发。
  • SubagentStop 在子智能体完成任务后触发。

2.4 完成事件

  • Stop 在Claude完成整轮响应时触发
  • Notification 在Claude发送系统通知时触发。

2.5 较新的事件类型

  • TeammateIdle与TaskCompleted 专为多智能体团队协作设计。前者在队友智能体即将进入空闲状态时触发,后者在任务被标记为完成时触发。
  • ConfigChange 在配置文件发生变更时触发。
  • WorktreeCreate与WorktreeRemove 分别对应GitWorktree的创建与删除操作。

2.6 “能否阻止”:最关键的维度

  • 具备阻止能力的事件包括PreToolUse、PermissionRequest、UserPromptSubmit、Stop、SubagentStop、TeammateIdle、TaskCompleted、ConfigChange、WorktreeCreate。
  • 其余事件(如PostToolUse、Notification、SubagentStart等)属于只读模式。它们主要用于读取上下文。注入额外的信息或触发侧边效应(如发送通知),但无法直接阻止或修改Claude的核心执行逻辑。
  • 在日常开发与运维中,最常用的3个事件是:PreToolUse(工具执行前的“守门员”)、PostToolUse(工具执行后的“质量守卫”)、Stop(任务完成时的“质148量门控”)。如果时间有限,优先精通PreToolUse、PostToolUse和Stop,即可构建出健壮的自动化闭环。

3.配置体系:6个位置,6种用途

  • 项目配置(.claude/settings.json)是团队协作的核心载体。将其提交至Git仓库后,所有成员在克隆项目时即可自动同步团队约定的安全检查与自动化规则。
  • 本地配置(.claude/settings.local.json)已被.gitignore忽略,适用于需要覆盖团队默认配置的个人场景。
  • 用户全局配置(~/.claude/settings.json)则用于管理跨项目的个人偏好,如自定义日志格式或桌面通知方式。

配置结构采用3层嵌套设计:事件类型→matcher组→Hook处理器列表。请看以下代码示例。

点击查看代码
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./.claude/hooks/block-dangerous.sh",
            "timeout": 30
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write \"$CLAUDE_FILE_PATH\""
          }
        ]
      }
    ]
  }
}

4.3种处理器类型:确定性的阶梯

4.1 command类型:确定性规则

点击查看代码
{
  "type": "command",
  "command": "./.claude/hooks/check-security.sh",
  "timeout": 30
}

4.2 prompt类型:单次大模型评估

点击查看代码
{
  "type": "prompt",
  "prompt": "评估这段代码修改是否引入了安全漏洞。$ARGUMENTS",
  "model": "claude-haiku-4-5",
  "timeout": 30
}

4.3 agent类型:多轮子智能体验证

点击查看代码
{
  "type": "agent",
  "prompt": "检查所有修改的文件是否通过了单元测试。运行测试套件并验证结果。$ARGUMENTS",
  "timeout": 120
}
  • 能用command类型的不用prompt类型,能用prompt类型的不用agent类型

5 hookSpecificOutput:与Claude交流的协议

点击查看代码
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "此命令试图删除受保护的系统目录",
    "additionalContext": "受保护的路径模式:/etc, usr, var"
  }
}
  • permissionDecision支持3种值。
    • allow:绕过权限系统直接执行。
    • deny:阻止执行。
    • ask:交由用户确认。
  • additionalContext字段适用于所有事件类型,其内容将被注入Claude的上下文中。
点击查看代码
{
  "continue": false,
  "stopReason": "检测到安全违规,会话已终止",
  "suppressOutput": false,
  "systemMessage": "警告:此操作已被安全策略拦截"
}

6 工程实战一:安全防护体系

6.1 PreToolUse:危险命令拦截

  • 第一道防线旨在拦截可能引发灾难的Bash命令。
点击查看代码
#!/bin/bash
# .claude/hooks/block-dangerous.sh
set -e

INPUT=$(cat)

# 提取命令(调试信息输出至 stderr)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
echo "DEBUG: Checking command: $COMMAND" >&2

# 危险命令模式列表
DANGEROUS_PATTERNS=(
  "rm -rf /"
  "rm -rf ~"
  "rm -rf \$HOME"
  "> devsd"
  "mkfs."
  ":(){:|:&};:"          # Fork bomb
  "chmod -R 777 /"
  "git push --force origin main"
  "git push --force origin master"
  "git reset --hard origin"
  "DROP DATABASE"
  "DROP TABLE"
  "TRUNCATE"
  "curl.*| sh"           # 危险的管道执行
  "curl.*| bash"
)

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if [[ "$COMMAND" == "$pattern" ]]; then
    echo "BLOCKED: $pattern" >&2
    cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "拦截危险命令模式: $pattern"
  }
}
EOF
    exit 2
  fi
done

echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
exit 0

6.2 PreToolUse:敏感文件保护

  • 第二道防线专注于保护敏感文件免受意外修改。
点击查看代码
#!/bin/bash
# .claude/hooks/protect-files.sh
set -e

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')

if [ -z "$FILE_PATH" ]; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
  exit 0
fi

FILENAME=$(basename "$FILE_PATH")

# 受保护的文件名模式
PROTECTED_FILES=(
  ".env"
  ".env.local"
  ".env.production"
  "credentials.json"
  "secrets.yaml"
  "secrets.json"
  "id_rsa"
  "id_ed25519"
)

# 受保护的扩展名
PROTECTED_EXTENSIONS=("pem" "key" "p12" "pfx")

# 受保护的目录
PROTECTED_DIRS=(".git/" ".ssh/" "node_modules/")

# 检查目录
for dir in "${PROTECTED_DIRS[@]}"; do
  if [[ "$FILE_PATH" == *"$dir"* ]]; then
    cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "不允许修改受保护目录中的文件: $dir"
  }
}
EOF
    exit 2
  fi
done

# 检查文件名
for name in "${PROTECTED_FILES[@]}"; do
  if [[ "$FILENAME" == "$name" ]]; then
    cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "不允许修改敏感文件: $name"
  }
}
EOF
    exit 2
  fi
done

# 检查扩展名
EXT="${FILENAME##*.}"
for ext in "${PROTECTED_EXTENSIONS[@]}"; do
  if [[ "$EXT" == "$ext" ]]; then
    cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "不允许修改密钥类文件: *.$ext"
  }
}
EOF
    exit 2
  fi
done

echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
exit 0

6.3 PostToolUse:全量操作审计

  • 第三道防线是全量操作审计。
点击查看代码
#!/bin/bash
# .claude/hooks/audit-log.sh

INPUT=$(cat)

LOG_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/logs"
mkdir -p "$LOG_DIR"

LOG_FILE="$LOG_DIR/audit-$(date +%Y-%m-%d).log"
TIMESTAMP=$(date -Iseconds)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')

echo "[$TIMESTAMP] $TOOL_NAME: $TOOL_INPUT" >> "$LOG_FILE"

echo '{}'
exit 0

6.4 完整配置

  • 将5.6.1小节到5.6.3小节的3个独立的脚本整合进.claude/settings.json,就形成了一套严密的纵深防御体系
点击查看代码
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./.claude/hooks/block-dangerous.sh"
          }
        ]
      },
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "./.claude/hooks/protect-files.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "./.claude/hooks/audit-log.sh"
          }
        ]
      }
    ]
  }
}
* 这套配置构建了一个涵盖“事前拦截、事中防护、事后审计”的完整安全闭环:危险命令拦截→敏感文件保护→全量操作审计。

7 工程实战二:代码质量自动化

7.1 PostToolUse:自动格式化

  • 每次Claude写入文件后,系统将自动触发格式化工具
点击查看代码
#!/bin/bash
# .claude/hooks/auto-format.sh
set -e

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')

if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
  echo '{}'
  exit 0
fi

EXTENSION="${FILE_PATH##*.}"

case "$EXTENSION" in
  js|jsx|ts|tsx|json|md|css|scss|html)
    if command -v npx &> /dev/null; then
      npx prettier --write "$FILE_PATH" 2> /dev/null
      echo '{"hookSpecificOutput":{"additionalContext":"已用 Prettier 格式化"}}'
    fi
    ;;
  py)
    if command -v black &> /dev/null; then
      black "$FILE_PATH" 2> /dev/null
      echo '{"hookSpecificOutput":{"additionalContext":"已用 Black 格式化"}}'
    fi
    ;;
  go)
    if command -v gofmt &> /dev/null; then
      gofmt -w "$FILE_PATH" 2> /dev/null
      echo '{"hookSpecificOutput":{"additionalContext":"已用 gofmt 格式化"}}'
    fi
    ;;
  *)
    echo '{}'
    ;;
esac

exit 0

7.2 PostToolUse:Lint反馈循环

  • 自动格式化确保了代码的“美观”,而Lint检查则保障了代码的“正确”。
点击查看代码
#!/bin/bash
# .claude/hooks/lint-check.sh
set -e

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')

if [[ "$FILE_PATH" == *.js || "$FILE_PATH" == *.ts || "$FILE_PATH" == *.jsx || "$FILE_PATH" == *.tsx ]]; then
  # 捕获 ESLint 输出,|| true 防止 set -e 导致脚本退出
  LINT_RESULT=$(npx eslint "$FILE_PATH" 2>&1) || true

  # 注意:由于使用了 || true,$? 永远为 0
  # 应通过检查 LINT_RESULT 是否为空来判断是否有 lint 错误
  if [ -n "$LINT_RESULT" ]; then
    ESCAPED=$(echo "$LINT_RESULT" | head -30 | jq -Rs '.')
    echo "{\"hookSpecificOutput\":{\"additionalContext\":\"ESLint 发现问题:\\n${ESCAPED}\"}}"
  else
    echo '{"hookSpecificOutput":{"additionalContext":"ESLint 检查通过"}}'
  fi
else
  echo '{}'
fi

exit 0

7.3 Stop Hook:测试质量门控

  • Stop Hook是质量保证的一道防线:当Claude宣称任务完成时,自动触发测试套件。如果测试失败,系统将阻止会话结束并强制要求继续修复。
点击查看代码
#!/bin/bash
# .claude/hooks/run-tests.sh

INPUT=$(cat)

# 【关键机制】防止无限循环:检查 stop_hook_active 标志
# 如果该标志为 true,说明已经重试过一次,本次必须放行以避免死锁
STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
if [ "$STOP_ACTIVE" = "true" ]; then
  exit 0  # 终止拦截,允许 Claude 停止
fi

# 切换到项目目录
if [ -n "$CLAUDE_PROJECT_DIR" ]; then
  cd "$CLAUDE_PROJECT_DIR"
fi

# 检测项目类型并运行测试
TEST_PASSED=true
TEST_RESULT=""

if [ -f "package.json" ] && grep -q '"test"' package.json; then
  TEST_RESULT=$(npm test 2>&1) || TEST_PASSED=false
elif [ -f "pyproject.toml" ] || [ -f "pytest.ini" ]; then
  TEST_RESULT=$(pytest 2>&1) || TEST_PASSED=false
elif [ -f "go.mod" ]; then
  TEST_RESULT=$(go test ./... 2>&1) || TEST_PASSED=false
else
  echo '{"hookSpecificOutput":{"additionalContext":"未检测到测试框架"}}'
  exit 0
fi

if [ "$TEST_PASSED" = true ]; then
  echo '{"hookSpecificOutput":{"additionalContext":"所有测试通过"}}'
else
  # 截取前50行错误日志并转义
  TEST_ESCAPED=$(echo "$TEST_RESULT" | head -50 | jq -Rs '.')

  # 返回 block 决策,强制 Claude 继续工作
  jq -n --argjson ctx "$TEST_ESCAPED" '{
    decision: "block",
    reason: "测试失败,请修复后再停止",
    hookSpecificOutput: {
      additionalContext: $ctx
    }
  }'
fi

exit 0

8 子智能体Hooks:精准的上下文管理

  • Hooks系统为此提供了两种专属事件——SubagentStart和SubagentStop。然而,更为关键的是第三种机制是直接在子智能体的Frontmatter中定义Hooks。

8.1 全局与Frontmatter:精度问题

点击查看代码
---
name: db-reader
description: 只读数据库分析工具
tools:
  - Read
  - Grep
  - Glob
  - Bash

hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./.claude/hooks/check-sql-injection.sh"

  Stop:
    - hooks:
        - type: prompt
          prompt: >-
            检查查询结果是否包含PII(如姓名、邮箱、手机号)。
            若包含,请回复 ok: false 并要求进行脱敏处理。

8.2 SubagentStart:自动注入上下文

  • SubagentStart Hook的典型应用场景是在子智能体启动时动态注入团队规范。
点击查看代码
{
  "hooks": {
    "SubagentStart": [
      {
        "matcher": "code-reviewer",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"SubagentStart\",\"additionalContext\":\"团队编码规范:使用camelCase命名,行长上限100个字符,公共API必须包含JSDoc注释\"}}'"
          }
        ]
      }
    ]

8.3 SubagentStop:验证输出质量

  • SubagentStop Hook可用于验证子智能体的工作成果是否达标。
点击查看代码
#!/bin/bash
# verify-review-quality.sh

INPUT=$(cat)
AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type')
STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active')

# 仅验证 code-reviewer 子智能体
if [ "$AGENT_TYPE" != "code-reviewer" ]; then
  exit 0
fi

# 防止死循环(若当前已是 Stop Hook 触发阶段,则跳过)
if [ "$STOP_ACTIVE" = "true" ]; then
  exit 0
fi

TRANSCRIPT=$(echo "$INPUT" | jq -r '.agent_transcript_path')

if [ -f "$TRANSCRIPT" ]; then
  HAS_ISSUES=$(grep -c "issue\|问题\|bug" "$TRANSCRIPT" || true)
  HAS_SUGGESTIONS=$(grep -c "suggest\|建议\|recommend" "$TRANSCRIPT" || true)

  if [ "$HAS_ISSUES" -gt 0 ] && [ "$HAS_SUGGESTIONS" -eq 0 ]; then
    echo '{"decision":"block","reason":"发现了问题但未提供修复建议,请补充每个问题的改进方案"}'
    exit 0
  fi
fi

exit 0
  • Frontmatter Hook:负责内部自检,确保子智能体自问“我的输出是否完整?”。
  • SubagentStart Hook:负责外部注入,在启动时赋予其必要的上下文(“给它必要的背景信息”)。
  • SubagentStop Hook:负责外部验收,在结束时严格核查“它的工作成果是否达标?”。

9 异步Hooks:后台执行不阻塞

  • 2026年年初发布的Claude Code引入了异步Hooks支持。
点击查看代码
{
  "type": "command",
  "command": "./.claude/hooks/run-tests-background.sh",
  "async": true,
  "timeout": 300
}
  • 异步Hooks的两个关键限制决定了其适用边界。
    • 类型限制:仅command类型的Hook支持异步执行。
    • 拦截能力限制:异步Hooks无法阻止当前操作。

  • 因此,异步Hooks适用于日志记录、异步通知、后台数据验证、非关键性质量审计等“事后处理”任务,不适用于需要实时阻断的安全检查(如SQL注入防御、敏感信息过滤)。

10 环境变量与调试

10.1 Hooks可用的环境变量

10.2 调试“三板斧”

  • 第一种,将调试信息输出至stderr。
  • 第二种,手动测试Hook脚本。
  • 第三种,使用claude --debug查看完整的Hook执行细节。

10.3 常见陷阱

11 工程设计方法论

  • 拦截时机(事件选择):操作前拦截,选用PreToolUse或UserPromptSubmit;操作后反馈,选用PostToolUse;完成时检查,选用Stop或SubagentStop;生命周期管理,选用SessionStart或SessionEnd。

  • 判断方式(类型选择):规则明确(如模式匹配、文件检查),选用command类型;需要语义判断但输入充分,选用prompt类型;需要深度代码分析,选用agent类型。

  • 配置作用域(位置选择):团队通用规范,配置于.claude/settings.json;个人偏好设置,配置于~/.claude/settings.json;子智能体专属检查,配置于Frontmatter。

  • 设计过程通常遵循“三步走”策略:
    第一步,首先配置基于PostToolUse事件且匹配器为matcher:"*"的审计日志Hook,以此观察Claude的实际工具调用模式,并积累数日的真实运行数据。
    第二步,基于审计数据识别高风险操作模式,进而设计针对性的PreToolUse拦截规则。
    第三步,逐步收紧拦截规则,同时始终保留日志记录功能,确保在发生误拦截时能够快速定位问题根源。

posted @ 2026-08-03 17:22  不知者buwei  阅读(0)  评论(0)    收藏  举报