Claude Code进阶——自动化工作流 Hooks

cuixiaogang

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": {
// 事件,claude支持多种事件,后续列出
"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 # stderr 变成 Claude 的反馈
exit 2 # exit 2 = 阻止操作
fi

exit 0 # exit 0 = 没有决策;正常权限流程适用

退出代码确定接下来会发生什么:

  • 代码0:hook 报告没有异议,操作正常进行。
  • 代码2:操作被阻止。写入原因到 stderr,Claude 会收到它作为反馈,以便它可以调整。
  • 其他退出代码:操作继续。成绩单显示 hook error 通知,后跟 stderr 的第一行;完整的 stderr 进入调试日志。

示例

任务完成后自动提交 git

使用的前提要求

  1. 当前项目存在git代码库
  2. 当前项目必须处于 $USER_develop 分支上(防止污染master分支)
  3. claude 存在个sesssion-id:827d5ccb-3bfe-4318-9546-7b45ed1058ae,主要目的是用于生成commit_message内容。使用命令claude --session-id 827d5ccb-3bfe-4318-9546-7b45ed1058ae既可生成对应的记录
  4. 需要部署一个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 message
2. 用户要求的是执行 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 message
5. 默认只输出最终提交信息文本,不附加解释

# 输出要求

生成的 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 pipefail

log() {
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"

# 如果 stop hook 已经处于激活状态,则直接退出,允许 Claude 正常停止
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"

# 生成锁文件路径:/tmp/项目目录(_代替/).lock
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"' EXIT
printf '%s\n' "$COMMIT_MESSAGE" > "$MSG_FILE"
git commit -F "$MSG_FILE"
rm -f "$MSG_FILE"
trap - EXIT

log "执行 git push origin $EXPECTED_BRANCH"
git push origin "$EXPECTED_BRANCH"

printf '已完成自动提交并推送:%s\n' "$COMMIT_MESSAGE"
  • Hook 配置:
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
# formatter.sh
set -euo pipefail

log() {
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"

# 如果 stop hook 已经处于激活状态,则直接退出,允许 Claude 正常停止
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
  • Hook 配置:
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"
}
]
}
]
}
}