本教程聚焦 Claude Code 里最被低估的自动化能力之一:用 Hooks 在每一次提问、每一次会话启动时,把项目的实时状态(Git 分支、最近提交、改动文件、测试结果、任务清单)自动注入到上下文里。学完后,你不再需要手动粘贴 git status、不需要把动态信息写进臃肿的 CLAUDE.md,Claude 会"自带雷达"地读懂当前项目环境。适合每天重度使用 Claude Code、希望减少机械操作、追求工程效能的开发者。
hookSpecificOutput 协议需较新版本支持)jq(用于解析 Hook 的 stdin JSON)settings.json 的三层位置(~/.claude/settings.json 用户级、.claude/settings.json 项目级、.claude/settings.local.json 个人本地级)很多人知道 CLAUDE.md 是 Claude Code 的长期记忆:启动时自动加载,每轮都在上下文里。它适合写不变的东西——项目约定、架构原则、命令清单。
但项目里大量信息是会变的:当前在哪个分支、最近三次提交是什么、工作区脏了几个文件、上一次测试有没有挂、TODO.md 里还剩几条没做。把这些塞进 CLAUDE.md 有两个硬伤:
Hooks 提供了另一条路:每次事件触发时,由脚本现算现注入。这就是"动态注入"。它的两个核心通道:
SessionStart:会话启动或 --resume 恢复时触发一次,适合注入"开机就要知道"的概览。UserPromptSubmit:用户每次提交 prompt、Claude 处理之前触发,适合注入"和这次提问相关"的增量状态。注入机制很简单:Hook 脚本把要注入的文本放进 hookSpecificOutput.additionalContext 字段,或直接 echo 到 stdout(退出码 0 时,UserPromptSubmit 和 SessionStart 的 stdout 会被加进上下文)。Claude 在"看到"你的提问之前,会先看到这段补充上下文。
先写一个最简单的开机注入脚本,确认管线打通。
操作说明:在项目根目录建 .claude/hooks/boot.sh:
#!/usr/bin/env bash
# .claude/hooks/boot.sh — SessionStart:开机注入项目概览
set -euo pipefail
cwd="$(jq -r '.cwd // "."' <<<"$(cat)")"
cd "$cwd" 2>/dev/null || exit 0
name="$(basename "$(pwd)")"
git rev-parse --is-inside-work-tree >/dev/null 2>&1 && {
branch="$(git rev-parse --abbrev-ref HEAD)"
echo "当前项目: ${name}(分支 ${branch})。请优先遵守 CLAUDE.md 中的规范。"
} || echo "当前项目: ${name}(非 Git 仓库)。"
在 .claude/settings.json 里注册:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/boot.sh" }
]
}
]
}
}
预期结果:重新启动 Claude Code(或在当前会话 /clear 后),用 Ctrl+R 进入 transcript 模式,能看到脚本 echo 出的那一行已经被注入。之后你随便问一句"我在哪个分支",Claude 不必再调用工具就能回答。
常见问题:
set -euo pipefail 注释掉,或在脚本最后加一行 echo "DEBUG done" >&2,stderr 会显示给用户。$CLAUDE_PROJECT_DIR 指向项目根更稳。开机注入是一次性的,真正好用的是"每次提问都刷新"。现在做一个 UserPromptSubmit Hook,把 Git 现状塞进去。
操作说明:建 .claude/hooks/radar.sh:
#!/usr/bin/env bash
# .claude/hooks/radar.sh — UserPromptSubmit:每次提问注入项目雷达
set -euo pipefail
input="$(cat)"
prompt="$(jq -r '.prompt // ""' <<<"$input")"
cwd="$(jq -r '.cwd // "."' <<<"$input")"
cd "$cwd" 2>/dev/null || exit 0
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
branch="$(git rev-parse --abbrev-ref HEAD)"
ab="$(git rev-list --left-right --count '@{u}...HEAD' 2>/dev/null \
| awk '{print "behind "$1" / ahead "$2}' || echo '无上游')"
last="$(git log --oneline -3 2>/dev/null | sed 's/^/ - /' || true)"
status="$(git status --porcelain 2>/dev/null || true)"
mod="$(printf '%s\n' "$status" | grep -c '^.M' || true)"
unt="$(printf '%s\n' "$status" | grep -c '^??' || true)"
ctx=$(cat <<EOF
[项目雷达] 分支: ${branch} (${ab})
最近提交:
${last}
工作区: 已修改 ${mod} / 未跟踪 ${unt}
EOF
)
jq -nc --arg c "$ctx" \
'{hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:$c}}'
把它注册到 settings.json 的 UserPromptSubmit 事件(结构和上面 SessionStart 一样,把事件名换掉即可)。
预期结果:你输入"帮我看看最近改了啥",Claude 在收到这句话的同时就拿到了分支、最近三次提交、脏文件数。它可以直接基于这些回答,而省去先调一次 Bash 跑 git status 的往返。这在交互体验上是质变——AI"知道现场"。
常见问题:
jq -nc --arg 来拼 JSON,不要手写字符串拼接。提交信息里有引号、反斜杠也不会炸。git log 的条数、status 只统计数量而不列全文。注入是"按字收费"的,别贪。@{u} 在没网或没上游时会慢。上面的 || echo 兜底了,仍建议在本地测试耗时。无脑注入所有信息会浪费 token。更聪明的做法是先看 prompt 说了什么,再决定注入什么。radar.sh 已经拿到了 prompt,加一段意图识别:
hint=""
case "$prompt" in
*测试*|*test*|*跑测*)
log=".last-test.log"
[ -f "$log" ] && hint="最近测试日志存在,建议先 tail -50 $log 确认是否失败。";;
*提交*|*commit*|*push*)
hint="用户可能要提交,已修改 ${mod} 个文件。建议先 review diff 再提交。";;
*重构*|*refactor*)
hint="用户可能要重构。建议先列出改动影响面再动手。";;
esac
[ -n "$hint" ] && ctx="$ctx"$'\n'"[提示] $hint"
预期结果:当你说"跑一下测试",上下文里就多出测试日志提示;说"准备提交",就多出 review 提示。无关提问则不注入,token 零浪费。
常见问题:
case 匹配中文/英文关键词够用;要更准可在脚本里调用一个小分类器或读 transcript_path 拿到更多上下文。但记住:Hook 要快,复杂的别放这里。把"动态信息"做厚一点:读取 TODO.md 的未完成项、读取最近一次测试/构建的错误。
# TODO 顶层未完成项(以 "- [ ]" 开头,取前 5 条)
if [ -f TODO.md ]; then
todos="$(grep -m5 '^- \[ \]' TODO.md | sed 's/^/ /' || true)"
[ -n "$todos" ] && ctx="$ctx"$'\n'"未完成 TODO:\n$todos"
fi
# 最近一次失败的测试痕迹
if [ -f .last-test.log ] && grep -qi 'fail\|error' .last-test.log; then
fails="$(grep -iE 'fail|error' .last-test.log | tail -3 | sed 's/^/ /')"
ctx="$ctx"$'\n'"[警告] 上次测试有失败:\n$fails"
fi
预期结果:Claude 一开始就知道还有哪些 TODO、上次测试挂在哪。你不用每次先交代背景。
常见问题:
- [ ] / - [x] 的 checkbox 语法,脚本解析才稳。.last-test.log 谁来写:可以用 PostToolUse Hook 在 Bash 跑完测试后自动落盘,或直接在测试命令后 | tee .last-test.log。-p 模式(headless)联动这是动态注入最被忽视的价值:-p 非交互模式同样会触发 SessionStart 和 UserPromptSubmit Hook。意味着你的批量任务、CI 脚本里的 claude -p "...",也能自动拿到项目雷达,而无需在 prompt 里手写一大段背景。
例如 CI 里:
claude -p "根据当前分支的改动,生成一段 PR 描述" \
--output-format json | jq -r '.result'
因为 radar.sh 已经把分支、提交、脏文件数注入了,这句 prompt 不必再啰嗦背景,输出也更贴合实际改动。
常见问题:
.claude/settings.json 和 .claude/hooks/ 都进了仓库,且 jq、git 在 CI 镜像里可用。@{u} 涉及远端比较)。动态注入是把双刃剑,管不好反而更费 token、更慢。三条铁律:
git diff 几千行)。配置层级也要分清:团队共享的注入逻辑放 .claude/settings.json(进版本库);个人偏好放 .claude/settings.local.json(gitignore),避免把个人雷达强加给同事。
把上面零散片段组装成一个可运行项目。目录结构:
your-repo/
├── .claude/
│ ├── settings.json
│ └── hooks/
│ ├── boot.sh # SessionStart:开机概览
│ └── radar.sh # UserPromptSubmit:实时雷达 + 意图注入
├── TODO.md
└── .last-test.log
.claude/settings.json(同时注册两个事件):
{
"hooks": {
"SessionStart": [
{ "matcher": "",
"hooks": [ { "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/boot.sh" } ] }
],
"UserPromptSubmit": [
{ "matcher": "",
"hooks": [ { "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/radar.sh" } ] }
]
}
}
radar.sh 完整版(合并意图识别 + TODO + 测试告警):
#!/usr/bin/env bash
set -euo pipefail
input="$(cat)"
prompt="$(jq -r '.prompt // ""' <<<"$input")"
cwd="$(jq -r '.cwd // "."' <<<"$input")"
cd "$cwd" 2>/dev/null || exit 0
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
branch="$(git rev-parse --abbrev-ref HEAD)"
ab="$(git rev-list --left-right --count '@{u}...HEAD' 2>/dev/null \
| awk '{print "behind "$1" / ahead "$2}' || echo '无上游')"
last="$(git log --oneline -3 | sed 's/^/ - /' || true)"
status="$(git status --porcelain)"
mod="$(printf '%s\n' "$status" | grep -c '^.M' || true)"
unt="$(printf '%s\n' "$status" | grep -c '^??' || true)"
ctx="[项目雷达] 分支: ${branch} (${ab})
最近提交:
${last}
工作区: 已修改 ${mod} / 未跟踪 ${unt}"
case "$prompt" in
*测试*|*test*|*跑测*)
[ -f .last-test.log ] && \
ctx="$ctx"$'\n'"[提示] 最近测试日志: .last-test.log";;
*提交*|*commit*|*push*)
ctx="$ctx"$'\n'"[提示] 可能要提交,先 review ${mod} 个文件的 diff";;
esac
[ -f TODO.md ] && {
todos="$(grep -m5 '^- \[ \]' TODO.md | sed 's/^/ /' || true)"
[ -n "$todos" ] && ctx="$ctx"$'\n'"未完成 TODO:\n$todos"
}
[ -f .last-test.log ] && grep -qi 'fail\|error' .last-test.log && {
fails="$(grep -iE 'fail|error' .last-test.log | tail -3 | sed 's/^/ /')"
ctx="$ctx"$'\n'"[警告] 上次测试失败:\n$fails"
}
jq -nc --arg c "$ctx" \
'{hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:$c}}'
演示运行:设当前在 feat/payment 分支、有 2 个已改文件、TODO.md 里有 3 条未完成、上次测试挂了一条。你输入"帮我把这块收尾",Claude 实际看到的上下文大致是:
[项目雷达] 分支: feat/payment (behind 0 / ahead 2)
最近提交:
- a1b2c3d 接入支付回调
- e4f5g6h 修复金额校验
- 7h8i9j0 初始化支付模块
工作区: 已修改 2 / 未跟踪 1
未完成 TODO:
- [ ] 支付失败重试
- [ ] 对账脚本
- [ ] 退款流程
Claude 因此能直接说"先把'支付失败重试'补上吧,它和你这次改动最相关"——而不是反问"你说的收尾指什么"。
flowchart TD
A[用户输入 prompt] --> B[UserPromptSubmit Hook 触发]
B --> C[radar.sh 读取 stdin JSON]
C --> D[计算 Git 状态/TODO/测试/意图]
D --> E[拼装 additionalContext]
E --> F{退出码}
F -->|0 + JSON| G[additionalContext 注入上下文]
F -->|2| H[阻断 prompt,stderr 提示用户]
G --> I[Claude 看到 prompt + 项目雷达]
I --> J[生成更贴合现场的回复]
flowchart LR
S1[SessionStart] -->|一次| O1[开机概览注入]
S2[UserPromptSubmit] -->|每轮| O2[实时状态 + 意图注入]
O1 --> CTX[共享上下文]
O2 --> CTX
CTX --> CC[Claude 处理]
PreToolUse 当护栏:和注入正交的另一条路是拦截。脚本返回 {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"禁止 rm -rf"}},可阻断危险命令;"allow" 则免去权限弹窗。transcript_path 做历史感知:注入脚本可以读会话 .jsonl,判断"这个话题已经聊过了"从而避免重复注入。-p 批量 + 注入:把雷达和 CI/批处理结合,每条 headless 任务都自带上下文,prompt 极短却信息完备。.claude/cache/*.json,Hook 只读本地文件,保持亚秒级。code.claude.com/docs/en/hookscode.claude.com/docs/en/hooks-guidecode.claude.com/docs/en/agent-sdk/hooksgithub.com/disler/claude-code-hooks-masterygist.github.com/FrancisBourre/50dca37124ecc43eaf08328cdcccdb34[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
