Claude Code 自动化与排错

Claude Code 自动化与排错

使用 Hooks 实现自动化

11 分钟阅读

用钩子自动化操作

在 Claude Code 编辑文件、完成任务或需要输入时自动运行 shell 命令。格式化代码、发送通知、校验命令,并强制执行项目规则。

钩子是在 Claude Code 生命周期特定时刻执行的用户自定义 shell 命令。它们对 Claude Code 的行为提供确定性的控制,确保某些操作始终发生,而不是依赖大模型自行选择是否运行它们。可以用钩子强制执行项目规则、自动化重复性任务,并将 Claude Code 与你现有的工具集成。

对于需要判断力、而非确定性规则的决策,你也可以使用基于提示词的钩子基于智能体的钩子,用一个 Claude 模型来评估条件。

关于扩展 Claude Code 的其他方式,请参阅技能(为 Claude 提供额外的指令和可执行命令)、子智能体(在隔离的上下文中运行任务),以及插件(打包扩展以跨项目共享)。

本指南涵盖常见用例和入门方法。关于完整的事件模式、JSON 输入输出格式,以及异步钩子和 MCP 工具钩子等高级特性,请参阅钩子参考文档

搭建你的第一个钩子

要创建一个钩子,在设置文件中添加一个 hooks 区块。本教程会创建一个桌面通知钩子,这样每当 Claude 在等待你的输入时,你都会收到提醒,而不必一直盯着终端。

1

将钩子添加到你的设置中

打开 ~/.claude/settings.json,添加一个 Notification 钩子。以下示例使用 macOS 上的 osascript;关于 Linux 和 Windows 的命令,请参阅当 Claude 需要输入时获得通知

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

如果你的设置文件已经有一个 hooks 键,请把 Notification 添加为现有事件键的同级项,而不是替换整个对象。每个事件名称都是同一个 hooks 对象中的一个键:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }]
      }
    ]
  }
}

你也可以在 CLI 中描述你想要的内容,让 Claude 帮你写这个钩子。

2

验证配置

输入 /hooks 打开钩子浏览器。你会看到所有可用钩子事件的列表,每个已配置钩子的事件旁边都会显示一个数量。选择 Notification,确认你的新钩子出现在列表中。选中该钩子会显示其详情:事件、匹配器、类型、源文件和命令。

3

测试该钩子

Esc 返回 CLI。让 Claude 做一件需要权限的事,然后切换离开终端。你应该会收到一条桌面通知。

/hooks 菜单是只读的。要添加、修改或移除钩子,请直接编辑你的设置 JSON,或让 Claude 帮你做这个更改。

你可以自动化什么

钩子让你能在 Claude Code 生命周期的关键节点运行代码:编辑后格式化文件、在命令执行前阻止它、在 Claude 需要输入时发送通知、在会话启动时注入上下文,等等。关于钩子事件的完整列表,请参阅钩子参考文档

每个示例都包含一个可直接使用的配置块,你可以把它添加到设置文件中。

关于运行一次独立模型审查、并将发现结果反馈回会话的钩子的生产环境示例,请参阅security-guidance 插件如何与 Claude Code 集成

当 Claude 需要输入时获得通知

每当 Claude 完成工作、需要你输入时,获得一条桌面通知,这样你就可以切换去做其他任务,而不必查看终端。

这个钩子使用 Notification 事件,它会在 Claude 等待输入或权限时触发。下面的每个标签页都使用了对应平台的原生通知命令。把它添加到 ~/.claude/settings.json

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

osascript 通过内置的 Script Editor 应用来发送通知。如果 Script Editor 没有通知权限,该命令会静默失败,macOS 也不会提示你授予权限。在终端中运行一次以下命令,让 Script Editor 出现在你的通知设置中:

osascript -e 'display notification "test"'

此时还不会出现任何内容。打开System Settings > Notifications,在列表中找到Script Editor,打开Allow Notifications。再次运行该命令,确认测试通知出现。

空的 matcher 会对所有通知类型触发。要只对特定事件触发,将其设为以下值之一:

匹配器触发时机
permission_promptClaude 需要你批准一次工具使用
idle_promptClaude 已完成,正在等待你的下一个提示词
auth_success身份验证完成
elicitation_dialog某个 MCP 服务器打开了一个征询表单
elicitation_complete某个 MCP 征询表单被提交或关闭
elicitation_response某个 MCP 征询回复被发送回该服务器
agent_needs_input某个后台会话开始等待你的输入。只在智能体视图打开时触发
agent_completed某个后台会话完成或失败。只在智能体视图打开时触发

agent_needs_inputagent_completed 匹配器需要 Claude Code v2.1.198 或更高版本。

输入 /hooks 并选择 Notification,确认该钩子已注册。关于完整的事件模式,请参阅Notification 参考

编辑后自动格式化代码

在 Claude 编辑的每个文件上自动运行Prettier,让格式保持一致,无需手动干预。

这个钩子使用带 Edit|Write 匹配器的 PostToolUse 事件,因此只在文件编辑工具之后运行。该命令用 jq 提取被编辑的文件路径,并将其传给 Prettier。把它添加到你项目根目录的 .claude/settings.json 中:

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

在 Claude Code v2.1.191 或更高版本上,你也可以把匹配器写成 Edit,Write,因为在这些版本上,|, 对工具名称匹配器而言是可互换的列表分隔符。

本页的 Bash 示例使用 jq 进行 JSON 解析。在 macOS 上用 brew install jq 安装,在 Debian 和 Ubuntu 上用 apt-get install jq 安装,或参阅jq 下载页面

阻止编辑受保护的文件

阻止 Claude 修改敏感文件,例如 .envpackage-lock.json,或 .git/ 中的任何内容。Claude 会收到解释为何该次编辑被阻止的反馈,从而可以调整其方案。

以下示例使用一个由该钩子调用的独立脚本文件。该脚本会将目标文件路径与一份受保护模式列表进行比对,并以退出码 2 退出以阻止该次编辑。

1

创建钩子脚本

将以下内容保存到 .claude/hooks/protect-files.sh

#!/bin/bash
# protect-files.sh

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 "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0
2

在 macOS 和 Linux 上将该脚本设为可执行

钩子脚本必须是可执行的,才能被 Claude Code 运行:

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

注册该钩子

.claude/settings.json 中添加一个 PreToolUse 钩子,在任何 EditWrite 工具调用之前运行该脚本:

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

压缩后重新注入上下文

当 Claude 的上下文窗口填满时,压缩会对对话进行摘要以释放空间。这可能会丢失重要细节。使用带 compact 匹配器的 SessionStart 钩子,在每次压缩后重新注入关键上下文。

你的命令写入 stdout 的任何文本都会被加入 Claude 的上下文。以下示例提醒 Claude 项目约定和近期的工作。把它添加到你项目根目录的 .claude/settings.json 中:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
          }
        ]
      }
    ]
  }
}

你可以把这里的 echo 换成任何能产出动态输出的命令,例如 git log --oneline -5 来展示最近的提交。要在每次会话启动时都注入上下文,可以考虑改用CLAUDE.md。关于环境变量,请参阅参考文档中的 CLAUDE_ENV_FILE

审计配置更改

追踪会话期间设置或技能文件何时发生变化。ConfigChange 事件会在外部进程或编辑器修改某个配置文件时触发,因此你可以为合规目的记录更改,或阻止未经授权的修改。

以下示例将每次更改追加到一份审计日志中。把它添加到 ~/.claude/settings.json

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

该匹配器按配置类型筛选:user_settingsproject_settingslocal_settingspolicy_settingsskills。要阻止某次更改生效,以退出码 2 退出,或返回 {"decision": "block"}。完整的输入模式请参阅ConfigChange 参考文档

目录或文件变化时重新加载环境

有些项目会根据你所在的目录设置不同的环境变量。像 direnv 这样的工具会在你的 shell 中自动完成这一点,但 Claude 的 Bash 工具不会自行获取这些变化。

SessionStart 钩子与 CwdChanged 钩子配合使用可以解决这个问题。SessionStart 会加载你启动所在目录的变量,CwdChanged 则在 Claude 每次切换目录时重新加载它们。两者都会写入 CLAUDE_ENV_FILE,Claude Code 会在每条 Bash 命令之前将其作为脚本前导运行。把它添加到 ~/.claude/settings.json

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ],
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

在每个带有 .envrc 的目录中运行一次 direnv allow,以允许 direnv 加载它。如果你使用 devbox 或 nix 而不是 direnv,同样的模式也适用,只需把 direnv export bash 换成 devbox shellenvdevbox global shellenv

要响应特定文件的变化,而不是每次目录变化,可以使用 FileChanged,并用一个以 | 分隔要监视的文件名的 matcher。在构建监视列表时,Claude Code 会把这个值拆分成字面文件名,而不是把它当作正则表达式来求值。关于这个值如何同时筛选文件变化时运行哪些钩子分组,请参阅FileChanged。以下示例监视工作目录中的 .envrc.env

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": ".envrc|.env",
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

关于输入模式、watchPaths 输出,以及 CLAUDE_ENV_FILE 的详情,请参阅CwdChangedFileChanged 参考条目。

自动批准特定的权限提示

对于你总是允许的工具调用,跳过批准对话框。以下示例自动批准 ExitPlanMode——这是 Claude 在完成展示计划、请求继续时调用的工具——这样每次计划就绪时你都不会被提示。

与上面基于退出码的示例不同,自动批准要求你的钩子向 stdout 写入一份 JSON 决策。PermissionRequest 钩子会在 Claude Code 即将显示一个权限对话框时触发,返回 "behavior": "allow" 就相当于代替你回答了它。

该匹配器把这个钩子限定为只针对 ExitPlanMode,因此不会影响其他提示。把它添加到 ~/.claude/settings.json

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

当该钩子批准时,Claude Code 会退出计划模式,并恢复你进入计划模式之前生效的权限模式。记录中会在原本会显示对话框的地方显示“Allowed by PermissionRequest hook”。钩子这条路径始终保留当前对话:它无法像对话框那样清空上下文并开始一个全新的实施会话。

要改为设置一个特定的权限模式,你钩子的输出可以包含一个带 setMode 条目的 updatedPermissions 数组。mode 值可以是任意权限模式,例如 defaultacceptEditsbypassPermissionsdestination: "session" 只会将其应用于当前会话。

bypassPermissions 只在该会话启动时就已经可以使用绕过模式的情况下才生效:即通过 --dangerously-skip-permissions--permission-mode bypassPermissions--allow-dangerously-skip-permissions,或设置中的 permissions.defaultMode: "bypassPermissions",且未被 permissions.disableBypassPermissionsMode 禁用。它绝不会被持久化为 defaultMode

要将该会话切换到 acceptEdits,你的钩子应向 stdout 写入以下 JSON:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedPermissions": [
        { "type": "setMode", "mode": "acceptEdits", "destination": "session" }
      ]
    }
  }
}

请让匹配器尽可能窄。匹配 .* 或留空该匹配器,会自动批准每一个权限提示,包括文件写入和 shell 命令。完整的决策字段集,请参阅PermissionRequest 参考文档

钩子的工作原理

钩子事件会在 Claude Code 生命周期的特定节点触发。当某个事件触发时,所有匹配的钩子会并行运行,完全相同的钩子命令会被自动去重。下表展示了每个事件及其触发时机:

事件触发时机
SessionStart会话开始或恢复时
Setup你用 --init-only,或在 -p 模式下用 --init--maintenance 启动 Claude Code 时。用于 CI 或脚本中的一次性准备工作
UserPromptSubmit你提交提示词、Claude 处理它之前
UserPromptExpansion用户输入的命令展开为一个提示词、到达 Claude 之前。可以阻止这次展开
PreToolUse某次工具调用执行之前。可以阻止它
PermissionRequest出现一个权限对话框时
PermissionDenied某次工具调用被自动模式分类器拒绝时。返回 {retry: true} 可以告诉模型可以重试该次被拒绝的工具调用
PostToolUse某次工具调用成功之后
PostToolUseFailure某次工具调用失败之后
PostToolBatch一整批并行工具调用全部完成之后、下一次模型调用之前
NotificationClaude Code 发送一条通知时
MessageDisplay助手消息文本正在显示期间
SubagentStart某个子智能体被生成时
SubagentStop某个子智能体完成时
TaskCreated某个任务正通过 TaskCreate 被创建时
TaskCompleted某个任务正被标记为已完成时
StopClaude 完成回复时
StopFailure该轮次因 API 错误而结束时。输出和退出码都会被忽略
TeammateIdle某个智能体团队成员即将闲置时
InstructionsLoaded某个 CLAUDE.md 或 .claude/rules/*.md 文件被加载进上下文时。会在会话启动时以及会话期间惰性加载文件时触发
ConfigChange会话期间某个配置文件发生变化时
CwdChanged工作目录发生变化时,例如 Claude 执行了一次 cd 命令。适合用像 direnv 这样的工具进行响应式环境管理
FileChanged磁盘上某个被监视的文件发生变化时。matcher 字段指定要监视哪些文件名
WorktreeCreate正通过 --worktreeisolation: "worktree" 创建一个 worktree 时。会取代默认的 git 行为
WorktreeRemove正在移除一个 worktree 时,无论是在会话退出时,还是某个子智能体完成时
PreCompact上下文压缩之前
PostCompact上下文压缩完成之后
Elicitation某个 MCP 服务器在工具调用期间请求用户输入时
ElicitationResult用户响应某个 MCP 征询之后、该回复被发送回该服务器之前
SessionEnd某个会话结束时

每个钩子都有一个决定其运行方式的 type。大多数钩子使用 "type": "command",运行一条 shell 命令。还有其他四种类型:

  • "type": "http":将事件数据以 POST 方式发往一个网址。请参阅HTTP 钩子
  • "type": "mcp_tool":调用一个已连接 MCP 服务器上的工具。请参阅MCP 工具钩子
  • "type": "prompt":单轮大模型评估。请参阅基于提示词的钩子
  • "type": "agent":带工具访问权限的多轮验证。智能体钩子是实验性功能,可能会发生变化。请参阅基于智能体的钩子

合并多个钩子的结果

当多个钩子匹配同一个事件时,每个钩子的命令都会运行到完成,然后 Claude Code 才会合并这些结果。一个钩子返回 deny,不会阻止其他钩子继续执行。不要依赖某个钩子的 deny 来抑制另一个钩子的副作用。

所有匹配的钩子都完成之后,Claude Code 会合并它们的输出。对于 PreToolUse 的权限决策,最严格的答案会生效,优先级顺序为 denydeferaskallow。来自 additionalContext 的文本会保留每个钩子的内容,一起传给 Claude。

以下示例在 Bash 上注册了两个 PreToolUse 钩子。第一个会将每条命令追加到一个日志文件,并以 0 退出。第二个运行一个脚本,当命令中包含 rm -rf 时以 2 退出拒绝:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r .tool_input.command >> ~/.claude/bash.log"
          },
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
          }
        ]
      }
    ]
  }
}

当 Claude 尝试运行 rm -rf /tmp/build 时,两个钩子会并行执行。日志钩子把该命令写入 ~/.claude/bash.log 并以 0 退出,即不表达任何决策。防护钩子以 2 退出,拒绝该次工具调用。拒绝优先,因此 Claude Code 会阻止该命令,并向 Claude 显示防护钩子的 stderr。由于日志钩子已经运行过,这条日志记录仍会被写入。

读取输入并返回输出

钩子通过 stdin、stdout、stderr 和退出码与 Claude Code 通信。当某个事件触发时,Claude Code 会把事件特定的数据以 JSON 形式传给你脚本的 stdin。你的脚本读取这份数据,完成工作,并通过退出码告诉 Claude Code 接下来该怎么做。

钩子输入

每个事件都包含像 session_idcwd 这样的通用字段,但每种事件类型还会附加不同的数据。例如,当 Claude 运行一条 Bash 命令时,PreToolUse 钩子会在 stdin 上收到类似这样的内容:

{
  "session_id": "abc123",          // unique ID for this session
  "cwd": "/Users/sarah/myproject", // working directory when the event fired
  "hook_event_name": "PreToolUse", // which event triggered this hook
  "tool_name": "Bash",             // the tool Claude is about to use
  "tool_input": {                  // the arguments Claude passed to the tool
    "command": "npm test"          // for Bash, this is the shell command
  }
}

你的脚本可以解析这份 JSON 并针对其中任何字段采取行动。UserPromptSubmit 钩子会改为获得 prompt 文本,SessionStart 钩子会获得一个值为 startupresumeclearcompactsource,以此类推。关于共享字段,请参阅参考文档中的通用输入字段,关于各事件特定的模式,请参阅其各自的章节。

钩子输出

你的脚本通过写入 stdout 或 stderr,并以特定的退出码退出,来告诉 Claude Code 接下来该怎么做。以下 PreToolUse 钩子会阻止一条命令:

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

if echo "$COMMAND" | grep -q "drop table"; then
  echo "Blocked: dropping tables is not allowed" >&2  # stderr becomes Claude's feedback
  exit 2                                               # exit 2 = block the action
fi

exit 0  # exit 0 = no decision; the normal permission flow applies

退出码决定了接下来会发生什么:

  • 退出码 0:该钩子表示没有异议,该操作正常继续。对于 PreToolUse 钩子,这并不等于批准该次工具调用:正常的权限流程仍然适用。对于 UserPromptSubmitUserPromptExpansionSessionStart 钩子,你写入 stdout 的任何内容都会被加入 Claude 的上下文。
  • 退出码 2:该操作被阻止。把原因写入 stderr,Claude 会将其作为反馈收到,从而可以调整方案。有些事件无法被阻止:对于 SessionStartSetupNotification 等事件,退出码 2 会向用户显示 stderr,执行会继续。完整列表请参阅各事件的退出码 2 行为
  • 任何其他退出码:该操作继续。记录中会显示一条 <hook name> hook error 提示,随后是 stderr 的第一行;完整的 stderr 会写入调试日志

结构化 JSON 输出

退出码只能让你阻止或保持沉默。要获得更精细的控制,可以改为退出码 0,并向 stdout 打印一个 JSON 对象。

用退出码 2 配合一条 stderr 消息来阻止,或用退出码 0 配合 JSON 实现结构化控制。不要混用两者:当你以退出码 2 退出时,Claude Code 会忽略 JSON。

例如,一个 PreToolUse 钩子可以拒绝某次工具调用并告诉 Claude 原因,或将其升级请求用户批准:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep for better performance"
  }
}

使用 "deny" 时,Claude Code 会取消该次工具调用,并将 permissionDecisionReason 反馈给 Claude。以下这些 permissionDecision 值专属于 PreToolUse

  • "allow":跳过交互式权限提示。拒绝和询问规则(包括企业统一管理的拒绝列表)仍然适用
  • "deny":取消该次工具调用,并把原因发给 Claude
  • "ask":像平常一样向用户显示权限提示

第四个值 "defer",可在带 -p 标志的非交互模式中使用。它会退出该进程,同时保留该次工具调用,以便某个 Agent SDK 包装层可以收集输入并恢复。请参阅参考文档中的延后处理一次工具调用

返回 "allow" 会跳过交互式提示,但不会覆盖权限规则。如果某条拒绝规则匹配该次工具调用,即使你的钩子返回了 "allow",该次调用仍会被阻止。如果某条询问规则匹配,用户仍会被提示。这意味着任何设置范围(包括统一管理设置)中的拒绝规则,始终优先于钩子的批准。

其他事件使用不同的决策模式。例如,PostToolUseStop 钩子使用一个顶层的 decision: "block" 字段,而 PermissionRequest 使用 hookSpecificOutput.decision.behavior。按事件分类的完整说明,请参阅参考文档中的汇总表

对于 UserPromptSubmit 钩子,请改用 additionalContext 将文本注入 Claude 的上下文。

type: "prompt" 的钩子对输出的处理方式不同:请参阅基于提示词的钩子

用匹配器筛选钩子

没有匹配器时,一个钩子会在其事件的每一次发生时触发。匹配器让你能缩小这个范围。例如,如果你只想在文件编辑之后运行一个格式化工具,而不是在每次工具调用之后都运行,请为你的 PostToolUse 钩子添加一个匹配器:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "prettier --write ..." }
        ]
      }
    ]
  }
}

"Edit|Write" 匹配器只在 Claude 使用 EditWrite 工具时触发,而不会在它使用 BashRead 或任何其他工具时触发。关于纯文本名称和正则表达式如何被求值,请参阅匹配器模式

Claude 也可以通过 Bash 工具运行 shell 命令来创建或修改文件。如果你的钩子必须捕获每一次文件变化(例如用于合规扫描或审计日志),请添加一个Stop 钩子,让它每轮扫描一次工作树。如果需要按每次调用覆盖,也要匹配 Bash,并让你的脚本用 git status --porcelain 列出已修改和未跟踪的文件。

每种事件类型都基于一个特定字段进行匹配:

事件匹配器筛选的内容匹配器取值示例
PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied工具名称BashEdit|Writemcp__.*
SessionStart会话启动方式startupresumeclearcompact
Setup触发搭建的 CLI 标志initmaintenance
SessionEnd会话结束原因clearresumelogoutprompt_input_exitbypass_permissions_disabledother
Notification通知类型permission_promptidle_promptauth_successelicitation_dialogelicitation_completeelicitation_responseagent_needs_inputagent_completed
SubagentStart智能体类型general-purposeExplorePlan,或自定义智能体名称
PreCompactPostCompact触发压缩的原因manualauto
SubagentStop智能体类型SubagentStart 相同的取值
ConfigChange配置来源user_settingsproject_settingslocal_settingspolicy_settingsskills
StopFailure错误类型rate_limitoverloadedauthentication_failedoauth_org_not_allowedbilling_errorinvalid_requestmodel_not_foundserver_errormax_output_tokensunknown
InstructionsLoaded加载原因session_startnested_traversalpath_glob_matchincludecompact
ElicitationMCP 服务器名称你已配置的 MCP 服务器名称
ElicitationResultMCP 服务器名称Elicitation 相同的取值
FileChanged要监视的字面文件名(参阅 FileChanged.envrc|.env
UserPromptExpansion命令名称你的技能或命令名称
UserPromptSubmitPostToolBatchStopTeammateIdleTaskCreatedTaskCompletedWorktreeCreateWorktreeRemoveCwdChangedMessageDisplay不支持匹配器每次发生都会触发

以下标签页展示了在不同事件类型上使用的更多匹配器。

只匹配 Bash 工具调用,并将每条命令记录到一个文件中。PostToolUse 事件会在命令完成后触发,因此 tool_input.command 中包含实际运行的内容。该钩子通过 stdin 以 JSON 形式接收事件数据,jq -r '.tool_input.command' 只提取命令字符串,>> 将其追加到日志文件中:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
          }
        ]
      }
    ]
  }
}

关于完整的匹配器语法,请参阅钩子参考文档

if 字段按工具名称和参数筛选

if 字段需要 Claude Code v2.1.85 或更高版本。更早的版本会忽略它,并在每次匹配的调用上运行该钩子。

if 字段使用权限规则语法,同时按工具名称和参数筛选钩子,因此只有当该次工具调用匹配时,该钩子进程才会启动。这超出了 matcher 的能力范围,因为 matcher 只在分组级别按工具名称筛选。

例如,以下配置只在 Claude 使用 git 命令时运行一个钩子,而不是对所有 Bash 命令都运行:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
          }
        ]
      }
    ]
  }
}

你的钩子命令是否运行,取决于你 if 模式的形状以及 Claude 正在调用的 Bash 命令:

if 模式Bash 命令钩子是否运行?原因
Bash(git *)git push命令名称匹配
Bash(git *)npm test && git push每个子命令都会被检查;git push 匹配
Bash(git *)echo $(git log)$() 和反引号内部的命令也会被检查;git log 匹配
Bash(git *)echo $(date)没有子命令匹配 git *
Bash(git push *)echo $(date)指定了超出命令名称的模式,无论如何都会在遇到 $()、反引号或 $VAR 时运行该钩子

当某条 Bash 命令无法被解析时,该筛选还会失效开放(fail open),无论模式如何都会运行你的钩子。由于这个筛选是尽力而为的,请使用权限系统而不是钩子来强制执行硬性的允许或拒绝。

if 字段接受与权限规则相同的模式:"Bash(git *)""Edit(*.ts)" 等等。要匹配多个工具名称,可以使用各自带有独立 if 值的独立处理器,或在支持竖线并列的 matcher 级别进行匹配。

if 只对工具类事件有效:PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied。将其添加到任何其他事件都会阻止该钩子运行。

配置钩子位置

你添加钩子的位置决定了它的范围:

位置范围是否可共享
~/.claude/settings.json你的所有项目否,仅限本机
.claude/settings.json单个项目是,可以提交到仓库
.claude/settings.local.json单个项目否,Claude Code 创建它时会被 gitignore
统一管理策略设置组织范围是,由管理员控制
插件hooks/hooks.json插件启用期间是,随插件打包
技能智能体 frontmatter该技能或智能体活动期间是,定义在该组件文件中

在 Claude Code 中运行 /hooks,可按事件分组浏览所有已配置的钩子。

要关闭钩子,在你的设置文件中设置 "disableAllHooks": true。在统一管理设置中配置的钩子仍会运行,除非那里也设置了 disableAllHooks

如果你在 Claude Code 运行期间直接编辑设置文件,文件监视器通常会自动获取钩子的变化。

基于提示词的钩子

对于需要判断力而非确定性规则的决策,使用 type: "prompt" 钩子。Claude Code 不会运行一条 shell 命令,而是把你的提示词和该钩子的输入数据发送给一个 Claude 模型(默认是 Haiku)来做决定。如果你需要更强的能力,可以用 model 字段指定一个不同的模型。

该模型唯一的任务是以 JSON 形式返回一个是/否决策:

  • "ok": true:该操作继续
  • "ok": false:接下来会发生什么取决于事件:
    • StopSubagentStopreason 会反馈给 Claude,让它继续工作
    • PreToolUse:该次工具调用被拒绝,reason 会作为工具错误返回给 Claude,让它可以调整并继续
    • PostToolUsePostToolBatchUserPromptSubmitUserPromptExpansion:该轮次结束,reason 会以一行警告的形式出现在聊天中

以下示例使用一个 Stop 钩子询问模型所有请求的任务是否已完成。如果模型返回 "ok": false,Claude 会继续工作,并把 reason 作为它的下一条指令:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
          }
        ]
      }
    ]
  }
}

关于完整的配置选项,请参阅参考文档中的基于提示词的钩子

基于智能体的钩子

智能体钩子是实验性功能。行为和配置可能会在未来版本中变化。对于生产环境的工作流,请优先使用命令钩子

当验证需要检查文件或运行命令时,使用 type: "agent" 钩子。与只进行一次大模型调用的提示词钩子不同,智能体钩子会生成一个子智能体,它可以读取文件、搜索代码,并使用其他工具在返回决策之前验证条件。

智能体钩子使用与提示词钩子相同的 "ok" / "reason" 响应格式,但默认超时时间更长,为 60 秒,最多允许 50 轮工具使用。

以下示例在允许 Claude 停止之前,验证测试是否通过:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

当钩子的输入数据本身足以做出决定时,使用提示词钩子。当你需要针对代码库的实际状态进行验证时,使用智能体钩子。

关于完整的配置选项,请参阅参考文档中的基于智能体的钩子

HTTP 钩子

使用 type: "http" 钩子,将事件数据以 POST 方式发往一个 HTTP 端点,而不是运行一条 shell 命令。该端点会收到与命令钩子在 stdin 上收到的相同的 JSON,并通过 HTTP 响应体、以相同的 JSON 格式返回结果。

当你希望由一个 Web 服务器、云函数或外部服务来处理钩子逻辑时,HTTP 钩子会很有用:例如,一个跨团队记录工具使用事件的共享审计服务。

以下示例将每次工具使用都 POST 到一个本地日志服务:

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/tool-use",
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

该端点应返回一个使用与命令钩子相同的输出格式的 JSON 响应体。要阻止某次工具调用,返回一个带有恰当 hookSpecificOutput 字段的 2xx 响应。单靠 HTTP 状态码无法阻止操作。

请求头的值支持用 $VAR_NAME${VAR_NAME} 语法进行环境变量插值。只有 allowedEnvVars 数组中列出的变量会被解析;所有其他 $VAR 引用会保持为空。

关于完整的配置选项和响应处理,请参阅参考文档中的HTTP 钩子

限制与故障排查

限制

设计钩子时请牢记以下约束:

  • 命令钩子只通过 stdout、stderr 和退出码通信。它们无法触发 / 命令或工具调用。通过 additionalContext 返回的文本会作为一条系统提醒被注入,Claude 会将其当作纯文本读取。HTTP 钩子则改为通过响应体通信。
  • 钩子超时时间因类型而异。可以用 timeout 字段(单位秒)对单个钩子进行覆盖。
    • commandhttpmcp_tool:10 分钟。UserPromptSubmit 会将其降为 30 秒,MessageDisplay 会将其降为 10 秒。
    • prompt:30 秒。
    • agent:60 秒。
  • PostToolUse 钩子无法撤销操作,因为该工具已经执行过了。
  • PermissionRequest 钩子在带 -p 标志的非交互模式下不会触发。请用 PreToolUse 钩子实现自动化的权限决策。
  • Stop 钩子只会在 Claude 完成回复时触发,而不仅仅是在任务完成时。它们不会在用户中断时触发。API 错误会改为触发 StopFailure
  • 当多个 PreToolUse 钩子返回 updatedInput 来重写某个工具的参数时,最后完成的那个会生效。由于钩子是并行运行的,这个顺序是不确定的。请避免让多个钩子修改同一个工具的输入。

钩子与权限模式

PreToolUse 钩子会在任何权限模式检查之前触发。一个返回 permissionDecision: "deny" 的钩子,即使在 bypassPermissions 模式下,或使用了 --dangerously-skip-permissions,也会阻止该工具。这让你可以强制执行用户无法通过更改权限模式来绕过的策略。

反过来则不成立:一个返回 "allow" 的钩子不会绕过设置中的拒绝规则。钩子可以收紧限制,但不能放宽到超出权限规则所允许的范围。

钩子没有触发

该钩子已配置,但从未执行。

  • 运行 /hooks,确认该钩子出现在正确的事件下
  • 检查该匹配器模式是否与工具名称精确匹配。匹配器区分大小写
  • 确认你触发的是正确的事件类型:PreToolUse 在工具执行之前触发,PostToolUse 在之后触发
  • 如果在带 -p 标志的非交互模式下使用 PermissionRequest 钩子,请改用 PreToolUse

输出中出现钩子错误

你在记录中看到类似“PreToolUse hook error: ...”的消息。

  • 你的脚本意外以非零退出码退出。通过传入示例 JSON 手动测试它:
    echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
    echo $?  # Check the exit code
  • 如果你看到“command not found”,请使用绝对路径或 ${CLAUDE_PROJECT_DIR} 来引用脚本。要完全避免 shell 引号问题,添加 "args": [],切换到exec 形式,直接生成该脚本,不经过 shell
  • 如果你看到“jq: command not found”,请安装 jq,或使用 Python/Node.js 进行 JSON 解析
  • 如果该脚本根本没有运行,请将其设为可执行:chmod +x ./my-hook.sh

/hooks 显示没有配置任何钩子

你编辑了一个设置文件,但该钩子没有出现在菜单中。

  • 文件编辑通常会被自动获取。如果几秒后仍未出现,文件监视器可能错过了这次变化:重启你的会话以强制重新加载。
  • 验证你的 JSON 是否有效:不允许有尾随逗号和注释
  • 确认设置文件位于正确的位置:项目钩子用 .claude/settings.json,全局钩子用 ~/.claude/settings.json

Stop 钩子触及阻止上限

Claude 持续工作而不停止,然后以一条警告结束该轮次,说明 Stop 钩子连续阻止的次数过多。

Claude Code 会在一个 Stop 钩子连续阻止八次而没有进展后覆盖它。你的钩子脚本需要检查它是否已经触发过一次续接。从 JSON 输入中解析 stop_hook_active 字段,如果为 true 则提前退出:

#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # Allow Claude to stop
fi
# ... rest of your hook logic

如果你的钩子确实需要超过八次迭代才能收敛,可以用 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 提高这个上限。

JSON 校验失败

即使你的钩子脚本输出了有效的 JSON,Claude Code 仍显示一个 JSON 解析错误。

当 Claude Code 运行一个 shell 形式的命令钩子(即不带 args 的钩子)时,默认会在 macOS 和 Linux 上生成 sh -c,或在 Windows 上生成 Git Bash。这个 shell 是非交互式的,但 Git Bash 以及某些配置(例如 BASH_ENV 指向 ~/.bashrc)仍会加载你的配置文件。如果该配置文件中包含无条件的 echo 语句,其输出会被添加到你钩子 JSON 的前面:

Shell ready on arm64
{"decision": "block", "reason": "Not allowed"}

Claude Code 会尝试将其解析为 JSON 并失败。要修复这个问题,在你的 shell 配置文件中包裹 echo 语句,使其只在交互式 shell 中运行:

# In ~/.zshrc or ~/.bashrc
if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

$- 变量包含 shell 标志,i 表示交互式。钩子运行在非交互式 shell 中,因此该 echo 会被跳过。

调试技巧

Ctrl+O 切换的记录视图,会为每个触发的钩子显示一行摘要:成功时静默,阻止性错误显示 stderr,非阻止性错误显示一条 <hook name> hook error 提示,随后是 stderr 的第一行。

要查看完整的执行详情(包括哪些钩子匹配、它们的退出码、stdout 和 stderr),请阅读调试日志。用 claude --debug-file /tmp/claude.log 启动 Claude Code,将其写入一个已知路径,然后在另一个终端中用 tail -f /tmp/claude.log。如果你启动时没有加这个标志,可以在会话中途运行 /debug 启用日志记录并找到日志路径。

了解更多

博极客AI是专业人工智能学习平台,提供通俗易懂的AI入门教程、大模型应用、实战项目与行业动态,全站内容免费阅览,零基础也能轻松学AI,适配学生、职场新人及技术爱好者。

© 版权所有 2026 博极客AI,保留一切权利。 | 桂ICP备2026007205号 | 桂公网安备45010502001169号