Claude Code进阶——自动化工作流 Hooks
Hooks 是用户自定义的 shell 命令,在 Claude Code 生命周期中的特定点执行。用户可以使用这个功能,来帮助完成诸如格式化代码、发送通知、验证命令、代码审计、git提交等一系列自动化流程。
如何配置 Hook 在 claude 的配置文件 中,添加如下代码既可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "hooks" : { "Notification" : [ { "matcher" : "" , "hooks" : [ { "type" : "command" , "command" : "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" } ] } ] } }
事件配置项 会话生命周期
事件名
中文说明
SessionStart
会话开始或恢复
Stop
当前轮正常结束
StopFailure
当前轮因 API 错误结束
SessionEnd
会话终止
用户输入相关
事件名
中文说明
备注
UserPromptSubmit
用户提交 prompt 时
可用于预处理用户输入
UserPromptExpansion
用户命令扩展成 prompt 时,可拦截
-
工具调用相关
事件名
中文说明
PreToolUse
工具执行前,可拦截
PostToolUse
工具执行成功后
PostToolUseFailure
工具执行失败后
PostToolBatch
一批并行工具执行完后
PermissionRequest
出现权限请求时
PermissionDenied
工具调用被拒绝时
消息与通知
事件名
中文说明
Notification
Claude Code 发送通知时
MessageDisplay
助手消息显示过程中
Agent/Task 相关
事件名
中文说明
SubagentStart
子代理启动时
SubagentStop
子代理结束时
TaskCreated
创建任务时
TaskCompleted
任务完成时
TeammateIdle
teammate 即将空闲时
配置/文件/环境相关
事件名
中文说明
InstructionsLoaded
规则文件被加载时
ConfigChange
配置文件变化时
CwdChanged
当前目录变化时
FileChanged
被监听文件变化时
Git Worktree 相关
事件名
中文说明
WorktreeCreate
创建 worktree 时
WorktreeRemove
删除 worktree 时
上下文压缩相关
事件名
中文说明
PreCompact
上下文压缩前
PostCompact
上下文压缩后
MCP 交互相关
事件名
中文说明
Elicitation
MCP 请求用户输入时
ElicitationResult
用户返回 MCP 输入结果后
Hook 的 type 类型 每个 Hook 都有一个 type 来确定它如何运行。大多数 Hooks 使用"type": "command",它运行 shell 命令。还有四种其他类型可用:
http:将事件数据 POST 到 URL。
mcp_tool:在已连接的 MCP 服务器上调用工具。
type:单轮 LLM 评估。
agent:具有工具访问权限的多轮验证。Agent hooks 是实验性的,可能会改变。
Hook 中的输入与输出 Hook 输入 自定义脚本中,能接收到 claude 传入的JSON参数(可使用cat命令获取),方便处理业务逻辑,不同的时间类型有不同的参数数据,以下为 Stop 事件的示例,其他事件可通过输出到文件中获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "session_id" : "efe3475b-5162-43e0-a3bf-3e5f3ab96b21" , "transcript_path" : "/home/cuixiaogang/.claude/projects/-data-cuixiaogang-service-data-gadmin/efe3475b-5162-43e0-a3bf-3e5f3ab96b21.jsonl" , "cwd" : "/data/cuixiaogang/service/data/gadmin/front" , "permission_mode" : "plan" , "effort" : { "level" : "high" } , "hook_event_name" : "Stop" , "stop_hook_active" : false , "last_assistant_message" : "最后与claude交互的内容" , "background_tasks" : [ ] , "session_crons" : [ ] }
Hook 输出 脚本通过写入 stdout 或 stderr 并以特定代码退出来告诉 Claude Code 接下来要做什么。例如:
1 2 3 4 5 6 7 8 9 10 #!/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 exit 2 fi exit 0
退出代码确定接下来会发生什么:
代码0:hook 报告没有异议,操作正常进行。
代码2:操作被阻止。写入原因到 stderr,Claude 会收到它作为反馈,以便它可以调整。
其他退出代码:操作继续。成绩单显示 hook error 通知,后跟 stderr 的第一行;完整的 stderr 进入调试日志。
示例 任务完成后自动提交 git 使用的前提要求 :
当前项目存在git代码库
当前项目必须处于 $USER_develop 分支上(防止污染master分支)
claude 存在个sesssion-id:827d5ccb-3bfe-4318-9546-7b45ed1058ae,主要目的是用于生成commit_message内容。使用命令claude --session-id 827d5ccb-3bfe-4318-9546-7b45ed1058ae既可生成对应的记录
需要部署一个commit_message的skills。
commit_message的skills(~/.claude/skills/generate-git-commit/SKILL.md):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 --- name: generate-commit-message description: 仅当用户明确要求“生成 commit message / 提交信息 / 提交说明”时使用。本 skill 会先读取当前仓库的 diff 信息,并结合项目信息生成可供用户手动使用的提交信息文本。只允许只读检查,不得执行任何会修改仓库状态的 git 操作。 --- 仅当用户明确要求生成 commit message、提交信息或提交说明时,才可使用本 skill。 # 用途 本 skill 用于根据当前仓库中实际发生的代码变更,生成可供用户手动使用的 commit message。 本 skill **只生成文本** ,**不执行任何会修改仓库状态的 git 或仓库相关操作** 。 # 触发条件 仅在以下情况下允许触发: 1. 用户明确要求生成 commit message、提交信息、提交说明、提交文案等2. 当前仓库中已有实际改动可供总结3. 用户意图是获取提交信息文本,而不是要求直接执行提交、推送或其他 git 操作# 允许的操作 为了准确生成提交信息,本 skill 可以执行以下**只读** 操作: - `git diff` - `git diff --cached` - `git status --short` - 读取必要的项目文件或项目信息(如 `package.json` 、`README` 、模块目录结构等),仅用于辅助理解改动上下文说明: 1. 应优先检查已暂存改动与未暂存改动的差异,必要时结合两者判断本次修改内容2. 应基于 `git diff` 的实际变化生成提交信息,而不是仅依据对话内容猜测3. 读取项目信息时应保持最小必要原则,只查看与理解改动直接相关的内容# 禁止事项 以下情况不得触发或不得执行: 1. 用户未明确要求生成 commit message2. 用户要求的是执行 commit / push / 切换分支 / 检查仓库状态之外的仓库操作3. 当前没有实际改动可总结4. 任何会修改仓库状态的 git 命令或仓库操作,包括但不限于: - `git add` - `git commit` - `git push` - `git pull` - `git checkout` - `git switch` - `git branch` - `git merge` - `git rebase` - `git reset` - `git restore` - `git stash` # 处理流程 1. 使用 `git diff` 和 / 或 `git diff --cached` 查看当前代码差异2. 如有必要,读取少量项目信息,帮助理解改动所属模块、功能或上下文3. 根据差异内容判断本次改动的主要目的4. 生成一条准确、简洁、可直接使用的 commit message5. 默认只输出最终提交信息文本,不附加解释# 输出要求 生成的 commit message 应满足: 1. 与当前仓库中的实际改动一致2. 优先反映“本次最核心的改动目的”,避免罗列过多细节3. 简洁、明确、可直接复制使用4. 默认优先使用单行格式5. 尽量符合 Conventional Commit 风格,如: - `feat: ...` - `fix: ...` - `refactor: ...` - `docs: ...` - `test: ...` - `chore: ...` 6. **尽量使用中文表达** ,除非以下情况可使用英文或保留英文: - 专有名词 - 约定俗成的技术术语 - 用户明确要求英文 - 使用中文会明显影响准确性或可读性# 默认输出格式 默认只输出一条可直接使用的 commit message,不附加解释、说明、命令示例或前后文。 例如: - `feat: 新增手动生成提交信息的 skill` - `fix: 修正提交信息生成逻辑中的差异判断` - `refactor: 优化基于 diff 的提交信息生成流程` - `docs: 补充提交信息 skill 的使用说明` # 歧义处理 如果用户表达不明确,例如“帮我处理提交”“收尾一下”“帮我提交”,不要触发本 skill,除非用户明确表示“只需要生成 commit message”。 如果当前 diff 同时包含多类无关改动,优先概括主要改动;若无法判断主次,可生成较中性的提交信息。 如果当前没有可识别的 diff 内容,不要编造提交信息,应明确说明当前没有足够的实际改动用于生成 commit message。 # 成功标准 本 skill 的成功标准只有一个:基于当前仓库的实际 diff 和必要的项目信息,产出准确、可直接使用的 commit message 文本。 不得执行或暗示已执行提交、推送、切换分支或其他会修改仓库状态的操作。
Hook 脚本(~/.claude/hooks/git-smart-commit.sh):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 #!/usr/bin/env bash set -euo pipefaillog () { printf '[hook] %s\n' "$*" >&2 } fail () { printf '[hook][error] %s\n' "$*" >&2 exit 1 } HOOK_JSON="$(cat) " if [[ -z "${HOOK_JSON} " ]]; then fail "未接收到 HOOK_JSON 输入" fi command -v jq >/dev/null 2>&1 || fail "缺少依赖:jq" command -v git >/dev/null 2>&1 || fail "缺少依赖:git" command -v claude >/dev/null 2>&1 || fail "缺少依赖:claude" command -v flock >/dev/null 2>&1 || fail "缺少依赖:flock" if [ "$(printf '%s' "$HOOK_JSON " | jq -r '.stop_hook_active') " = "true" ]; then exit 0 fi SESSION_ID="$(printf '%s' "$HOOK_JSON " | jq -r '.session_id // empty') " CWD="$(printf '%s' "$HOOK_JSON " | jq -r '.cwd // empty') " HOOK_EVENT_NAME="$(printf '%s' "$HOOK_JSON " | jq -r '.hook_event_name // empty') " LAST_ASSISTANT_MESSAGE="$(printf '%s' "$HOOK_JSON " | jq -r '.last_assistant_message // empty') " STOP_HOOK_ACTIVE="$(printf '%s' "$HOOK_JSON " | jq -r '.stop_hook_active // false') " [[ -n "$SESSION_ID " ]] || fail "HOOK_JSON 中缺少 session_id" [[ -n "$CWD " ]] || fail "HOOK_JSON 中缺少 cwd" if [[ "$HOOK_EVENT_NAME " != "Stop" ]]; then exit 0 fi if [[ "$STOP_HOOK_ACTIVE " == "true" ]]; then log "检测到 stop_hook_active=true,跳过执行,允许 Claude 正常停止" exit 0 fi cd "$CWD " || fail "无法进入 cwd: $CWD " GIT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true) " [[ -n "$GIT_ROOT " ]] || fail "当前目录不在 git 仓库中:$CWD " cd "$GIT_ROOT " || fail "无法进入 git 根目录:$GIT_ROOT " LOCK_NAME="$(printf '%s' "$GIT_ROOT " | sed 's#/#_#g') .lock" LOCK_FILE="/tmp/${LOCK_NAME} " log "git root: $GIT_ROOT " log "lock file: $LOCK_FILE " exec 9>"$LOCK_FILE " if ! flock -n 9; then log "已有其他 hook 实例在执行,跳过本次运行" exit 0 fi if [[ -z "${USER:-} " ]]; then fail "环境变量 USER 未设置,无法校验目标分支" fi CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD) " EXPECTED_BRANCH="${USER} _develop" if [[ "$CURRENT_BRANCH " != "$EXPECTED_BRANCH " ]]; then fail "当前分支为 '$CURRENT_BRANCH ',不是期望分支 '$EXPECTED_BRANCH ',终止执行" fi if [[ -z "$(git status --porcelain) " ]]; then fail "当前仓库没有可提交的变更,终止执行" fi PROMPT='调用你的 generate-commit-message skill。只输出最终的 commit message 内容。允许多行。不要输出解释,不要输出代码块,不要输出前后引号,不要输出多余文本。提交信息尽量使用中文。' log "开始生成 commit message" COMMIT_MESSAGE="$( claude --resume "827d5ccb-3bfe-4318-9546-7b45ed1058ae" -p "$PROMPT " | tr -d '\r' ) "if [[ -z "$(printf '%s' "$COMMIT_MESSAGE " | tr -d '[:space:]') " ]]; then fail "生成的 commit message 为空" fi log "commit message: $COMMIT_MESSAGE " log "执行 git add ." git add . if git diff --cached --quiet; then fail "git add 后暂存区无变更,终止执行" fi log "执行 git commit" MSG_FILE="$(mktemp) " trap 'rm -f "$MSG_FILE"' EXITprintf '%s\n' "$COMMIT_MESSAGE " > "$MSG_FILE " git commit -F "$MSG_FILE " rm -f "$MSG_FILE " trap - EXITlog "执行 git push origin $EXPECTED_BRANCH " git push origin "$EXPECTED_BRANCH " printf '已完成自动提交并推送:%s\n' "$COMMIT_MESSAGE "
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { "permissions" : { "allow" : [ "Read" , "Search" , "Bash(gofmt -w *)" , "Bash(sed *)" , "Bash(find:*)" , "Bash(git add *)" , "Bash(git commit *)" , "Bash(git push origin *_develop)" , "Bash(git status *)" , "Bash(git diff *)" , "Bash(cd *poodle && git add *)" ] } , "hooks" : { "Stop" : [ { "matcher" : "Edit|Write" , "hooks" : [ { "type" : "command" , "command" : "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/git-smart-commit.sh" } ] } ] } }
vue代码自动格式化
Hook 脚本(~/.claude/hooks/formatter.sh):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #!/bin/bash set -euo pipefaillog () { printf '[hook] %s\n' "$*" >&2 } fail () { printf '[hook][error] %s\n' "$*" >&2 exit 1 } HOOK_JSON="$(cat) " if [[ -z "${HOOK_JSON} " ]]; then fail "未接收到 HOOK_JSON 输入" fi command -v jq >/dev/null 2>&1 || fail "缺少依赖:jq" if [ "$(printf '%s' "$HOOK_JSON " | jq -r '.stop_hook_active') " = "true" ]; then log "检测到 stop_hook_active=true,跳过执行" exit 0 fi if [ "$(printf '%s' "$HOOK_JSON " | jq -r '.hook_event_name') " != "Stop" ]; then log "检测到 hook_event_name!=Stop,跳过执行" exit 0 fi FRONT_DIR="${CLAUDE_PROJECT_DIR} /front" if [[ -d "$FRONT_DIR " && -f "$FRONT_DIR /package.json" ]]; then log "格式化前端代码: npm run format" cd "$FRONT_DIR " && npm run format --silent 2>&1 | head -5 >&2 fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 { "permissions" : { "allow" : [ "Bash(ls:*)" , "Bash(find:*)" , "Bash(cat:*)" , "Bash(head:*)" , "Bash(tail:*)" , "Bash(grep:*)" , "Bash(wc:*)" , "Bash(echo:*)" , "Bash(pwd:*)" , "Bash(sort:*)" , "Bash(uniq:*)" , "Skill(update-config)" , "Read" , "Update" , "Write" ] } , "hooks" : { "Stop" : [ { "matcher" : "Edit|Write" , "hooks" : [ { "type" : "command" , "command" : "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/formatter.sh" } ] } ] } }