Claude Code Hooks 自动化工作流指南:通知、格式化、文件保护、审计与环境变量

Hooks 是用户定义的 shell 命令,在 Claude Code 生命周期特定节点自动执行——保证操作必然发生,不依赖 LLM 选择。本文覆盖最常用场景:编辑后自动格式化(PostToolUse + Edit|Write)、保护敏感文件不被修改(PreToolUse + exit 2)、上下文压缩后重注入关键信息(SessionStart + compact matcher)、配置变更审计日志、目录切换时通过 CwdChanged 重载环境变量、自动批准特定权限请求。每个场景附可直接复制的 JSON 配置块,并包含 matcher 语法、输入输出格式、调试技巧和常见错误排查。

Hooks 让你对 Claude Code 的行为有确定性控制。与依赖 LLM 决定是否执行的 CLAUDE.md 指令不同,hooks 在匹配事件发生时必然运行。可以用 hooks 强制执行项目规则、自动化重复任务、集成现有工具链。

对需要判断力(而非确定性规则)的场景,可以使用 prompt-based hooksagent-based hooks,让 Claude 模型评估条件。

其他扩展方式:skills(额外指令和可执行命令)、sub-agents(隔离上下文运行任务)、plugins(打包扩展跨项目共享)。

本文覆盖常用场景和入门步骤。完整事件 schema、JSON 输入输出格式、异步 hooks 和 MCP tool hooks 见 Hooks 参考

配置第一个 Hook

创建 Hook 需在设置文件中添加 hooks 块。下面以“桌面通知”演示:当 Claude 等待你输入时发送通知,这样就不必一直盯着终端。

第一步:添加配置

打开 ~/.claude/settings.json,添加 Notification hook:

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

如果你的设置文件已有 hooks 键,将 Notification 作为已有事件键的兄弟添加,不要替换整个对象:

{
  "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\"'" }]
      }
    ]
  }
}

第二步:确认 hook 注册

在 CLI 中键入 /hooks 打开 hooks 浏览器,可以看到所有可用 hook 事件,已配置的事件旁边有计数。选择 Notification 确认新 hook 出现。

第三步:测试通知

Esc 返回 CLI,让 Claude 做需要权限的操作,然后切换离开终端,桌面应出现通知。

macOS 通知不显示?osascript 通过 Script Editor 发送通知。如果 Script Editor 没有通知权限,命令会静默失败。在终端运行 osascript -e 'display notification "test"',然后打开 系统设置 → 通知,找到 Script Editor,开启允许通知

常用自动化场景

每个场景包含可直接使用的配置块,添加到设置文件中。

获取输入时通知

使用 Notification 事件,在 Claude 等待输入或权限时发送桌面通知。空 matcher 匹配所有通知类型。要只针对特定事件,设置 matcher 为以下值之一:

Matcher 作用
permission_prompt Claude 等待你批准工具使用
idle_prompt Claude 完成工作等待你下一条指令
auth_success 认证完成
elicitation_dialog MCP 服务器打开一个提醒表单
elicitation_complete MCP 提醒表单被提交或关闭
elicitation_response MCP 提醒回复发回给服务器

Linux 配置:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
          }
        ]
      }
    ]
  }
}

Windows 配置:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
          }
        ]
      }
    ]
  }
}

编辑后自动格式化代码

PostToolUse 事件配合 Edit|Write matcher,只在文件编辑工具执行后触发。命令用 jq 提取文件路径传给 Prettier。

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

Bash 示例中用到 jq,安装:brew install jq (macOS)、apt-get install jq (Debian/Ubuntu)。

阻止编辑受保护文件

防止 Claude 修改 .envpackage-lock.json.git/ 等文件。用独立脚本检查路径,匹配则 exit 2 拦截。

创建脚本 .claude/hooks/protect-files.sh

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

赋予执行权限

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

注册 PreToolUse hook

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

上下文压缩后重注入关键信息

SessionStart hook 配合 compact matcher,在每次上下文压缩后自动注入项目惯例或近期工作。stdout 输出被加入 Claude 上下文。

{
  "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 事件在外部进程或编辑器修改配置文件时触发,可记录变更用于合规或拦截。

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

matcher 过滤配置类型:user_settingsproject_settingslocal_settingspolicy_settingsskills。exit 2 或返回 {"decision": "block"} 可阻止变更。详见 ConfigChange 参考

目录切换时重载环境变量

SessionStart + CwdChanged 配合 direnv 让 Claude 的 Bash 工具感知目录环境变量。写入 CLAUDE_ENV_FILE 的值会作为每条 Bash 命令的前置脚本执行。

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

先在各目录执行 direnv allow 。如果用 devbox 或 nix,替换 direnv export bashdevbox shellenvdevbox global shellenv

若要监控特定文件变更(而非仅目录切换),用 FileChanged 事件:

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

自动批准特定权限请求

跳过总是允许的工具权限对话框。返回 JSON 写明 "behavior": "allow",matcher 精确限定范围防止过度授权。

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

若要同时切换权限模式,返回 JSON 中包含 updatedPermissions 数组:

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

bypassPermissions 模式仅当会话启动时已启用该模式时才可用(通过 --dangerously-skip-permissions 等),且未被 permissions.disableBypassPermissionsMode 禁用。它不会被持久化为 defaultMode

Hooks 工作原理

Hook 事件在 Claude Code 生命周期特定点触发。事件触发时,所有匹配的 hooks 并行执行,相同命令自动去重。

事件列表

事件 触发时机
SessionStart 会话开始或恢复
Setup 使用 --init-only--init--maintenance 启动时,用于 CI 或脚本的一次性准备
UserPromptSubmit 用户提交 prompt 后、Claude 处理前
UserPromptExpansion 用户键入的命令展开为 prompt 后、到达 Claude 前
PreToolUse 工具调用执行前,可以拦截
PermissionRequest 权限对话框弹出时
PermissionDenied 自动模式分类器拒绝工具调用后
PostToolUse 工具调用成功后
PostToolUseFailure 工具调用失败后
PostToolBatch 一批并行工具调用全部解析后、下一次模型调用前
Notification Claude Code 发出通知时
SubagentStart 子代理被创建时
SubagentStop 子代理完成时
TaskCreated 通过 TaskCreate 创建任务时
TaskCompleted 任务标记为完成时
Stop Claude 完成响应时
StopFailure 本次结束因 API 错误终止时
TeammateIdle agent team 同事即将闲置时
InstructionsLoaded CLAUDE.md.claude/rules/*.md 加载到上下文时
ConfigChange 配置文件在会话中变更时
CwdChanged 工作目录改变时(如 cd 命令)
FileChanged 被监测的文件在磁盘上变更时
WorktreeCreate 通过 --worktreeisolation: "worktree" 创建工作树时
WorktreeRemove 工作树被移除时(会话结束或子代理完成)
PreCompact 上下文压缩前
PostCompact 上下文压缩完成后
Elicitation MCP 服务器在工具调用中请求用户输入时
ElicitationResult 用户响应 MCP 提醒后、回复发回服务器前
SessionEnd 会话终止时

Hook 类型

  • "type": "command":运行 shell 命令
  • "type": "http":POST 事件数据到 URL
  • "type": "mcp_tool":调用已连接 MCP 服务器的工具
  • "type": "prompt":单轮 LLM 评估
  • "type": "agent":多轮验证(实验性,可能变化)

多 Hook 结果合并

多个 hooks 匹配同一事件时,所有命令执行完成后再合并结果。一个 hook 返回 deny 不会阻止其他 sibling hook 执行。对于 PreToolUse 的决策,最严格的生效:deny > ask > allow。additionalContext 文本全部保留并传给 Claude。

输入输出机制

Hooks 通过 stdin 接收事件特定 JSON,通过 stdout/stderr 和退出码通信。

输入示例(PreToolUse)

{
  "session_id": "abc123",
  "cwd": "/Users/sarah/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

输出通过退出码

  • 0:无异议,正常进行(非自动批准,权限流仍适用)
  • 2:拦截操作,stderr 内容作为反馈传给 Claude
  • 其他:操作继续,转录显示 <hook name> hook error 注意

结构化 JSON 输出(exit 0 时输出到 stdout):

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

permissionDecision 值:allow(跳过交互确认,但不覆盖 deny 规则)、denyaskdefer 仅在非交互模式可用。

返回 "allow" 不会覆盖设置中的 deny 规则,包括企业托管 deny 列表。钩子只能收紧限制,不能放松。

UserPromptSubmit 事件不返回决定,而是通过 additionalContext 注入文本。

Matcher 过滤器

无 matcher 时钩子匹配事件所有实例。不同事件 matcher 过滤以下字段:

事件 过滤字段 示例值
PreToolUse, PostToolUse, PermissionRequest 工具名称 Bash, Edit|Write, mcp__.*
SessionStart 启动方式 startup, resume, clear, compact
Setup CLI 标志 init, maintenance
SessionEnd 结束原因 clear, resume, logout
Notification 通知类型 permission_prompt, idle_prompt
ConfigChange 配置来源 user_settings, project_settings
FileChanged 文字文件名( 分隔)
StopFailure 错误类型 rate_limit, authentication_failed
UserPromptExpansion 命令名 技能或命令名称
Elicitation MCP 服务器名 你的 MCP 服务器名称

事件 UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged 不支持 matcher,每次事件都会触发。

if 字段同时过滤工具名称和参数(需要 Claude Code v2.1.85+):

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

if 只对工具事件(PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied)有效,其他事件添加 if 会阻止钩子运行。

配置文件位置

位置 范围 可否共享
~/.claude/settings.json 所有项目 本机
.claude/settings.json 单项目 可提交到仓库
.claude/settings.local.json 单项目 gitignore 不共享
托管策略设置 组织范围 管理员控制
插件 hooks/hooks.json 插件启用时 捆绑在插件中
技能代理 frontmatter 技能或代理激活时 在组件文件中定义

运行 /hooks 浏览所有配置的 hooks,按事件分组。在设置文件中设 "disableAllHooks": true 可禁用所有 hooks(托管设置中的 hooks 仍运行,除非也在那里设置)。

Prompt-based Hooks

对于需要判断力的决策,使用 type: "prompt" hooks。Claude Code 将你的 prompt 和输入数据发给 Claude 模型(默认 Haiku),返回 JSON 决定:{"ok": true}{"ok": false, "reason": "..."}

不同事件中 ok: false 的效果:

  • Stop, SubagentStopreason 反馈给 Claude 让它继续工作
  • PreToolUse:工具调用被拒绝,reason 作为工具错误返回给 Claude
  • PostToolUse, PostToolBatch, UserPromptSubmit, UserPromptExpansion:本次轮次结束,reason 作为警告行显示在对话中

示例:Stop hook 检查任务是否完成,未完成则让 Claude 继续:

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

完整配置选项见 Prompt-based hooks 参考

Agent-based Hooks

实验性功能,行为可能在将来版本中变更。生产环境优先使用 command hooks。

当需要读取文件或运行命令来验证条件时,用 type: "agent" hooks。子代理可以读文件、搜索代码、使用工具,然后返回决策。超时默认 60 秒,最多 50 个工具使用轮次。

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

HTTP Hooks

type: "http" hooks 将事件数据 POST 到 HTTP 端点,端点在响应体中返回结果。适合将逻辑放在 Web 服务器或云函数中。

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

端点应返回 JSON 响应体,格式与 command hooks 相同。HTTP 状态码不能单独拦截,需响应体字段。

限制与故障排查

限制

  • Command hooks 只能通过 stdout/stderr/退出码通信,不能触发 / 命令或工具调用。HTTP hooks 通过响应体通信。
  • 超时:command/http/mcp_tool 默认 10 分钟(UserPromptSubmit 时降为 30 秒),prompt 30 秒,agent 60 秒。可用 timeout 字段按钩子覆盖。
  • PostToolUse 无法撤销已执行的工具操作。
  • PermissionRequest 在非交互模式(-p)下不触发,应使用 PreToolUse
  • Stop hooks 在 Claude 完成响应时触发(非仅任务完成),用户中断时不触发。API 错误触发 StopFailure
  • 多个 PreToolUse hooks 返回 updatedInput 修改工具参数时,最后一个完成的生效(并行非确定),避免多个钩子修改同一工具的输入。

Hooks 与权限模式

PreToolUse hooks 在权限模式检查前执行。返回 "deny" 即使在 bypassPermissions 模式下也会拦截。返回 "allow" 不会覆盖设置中的 deny 规则。

Hook 不触发

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

Hook 错误输出

  • 脚本非零退出。手动测试:echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $?
  • “command not found”:使用绝对路径或 $CLAUDE_PROJECT_DIR,或添加 "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 hook 达到阻塞上限

连续阻塞 8 次后 Claude Code 会覆盖 Stop hook。钩子脚本应检查 stop_hook_active 字段是否为 true,是则提前 exit 0:

#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
# ... 其余钩子逻辑

如需更多迭代,用环境变量 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 提高上限。

JSON 验证失败

非交互 shell 可能因 shell profile 中的 echo 输出污染 JSON 输出。将 profile 中的 echo 包裹在交互式 shell 检查内:

if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

调试技巧

  • Ctrl+O 打开转录视图:成功静默,阻断错误显示 stderr,非阻断错误显示 <hook name> hook error 注意加首行 stderr。
  • 启动时 claude --debug-file /tmp/claude.log,然后 tail -f /tmp/claude.log 查看详情。会话中可用 /debug 启用日志并显示日志路径。

深入学习

常见问题

Hooks 和 CLAUDE.md 有什么区别,什么时候该用哪个?

CLAUDE.md 是静态指令,Claude 读取后“知道”要怎么做,但不保证每次都会执行。Hooks 是确定性的,事件触发时必然运行,不依赖 LLM 判断。需要“每次都要发生”的操作(格式化、通知、保护文件)用 Hooks;需要 Claude 理解项目规范用 CLAUDE.md

Hook 命令可以访问哪些上下文信息?

通过 stdin 以 JSON 格式接收事件数据。不同事件包含不同字段,例如 PreToolUse 包含 tool_nametool_inputSessionStart 包含 source(startup/resume/clear/compact)。用 jq 或脚本解析。完整字段列表见 Hooks 参考

非交互模式(-p)下如何自动批准权限请求?

PermissionRequest hooks 在 -p 模式下不触发。应改用 PreToolUse hook,返回 permissionDecision: "allow" 来跳过交互确认。注意 allow 不能覆盖设置中的 deny 规则。