把 Claude Code 的 -p 无头模式、Hooks 事件系统、Subagent 批量派活这三件套拧成一根绳,是规模化自动化最难也最值钱的一步。本教程不重复"单个钩子拦截一条命令"的入门玩法,而是聚焦 2026 年 Hooks 参考里真正改变批量任务形态的三个新机制:PostToolBatch 聚合判定、agent-based hook(用模型做判断)、async hook(后台非阻塞)。学完你将拥有一条可复现的批量代码审查流水线:用 -p 管道把成百上千个 diff 喂进去,靠聚合钩子在"一批并行工具调用落定、下一轮推理之前"这个唯一正确的时机把关,再用后台钩子跑测试,全程无人值守。适合已经会用 claude -p 跑单任务、想把审查从"一次一个"升级到"成批流水"的后端/平台/SRE 工程师。
--bare 行为、stdin 10MB 上限、后台任务宽限期都基于 v2.1.163 前后的约定,PostToolBatch/agent hook/async hook 是更近版本引入的,越新越全。settings.json、会用 jq 解析流式 JSON。git diff、有可跑的测试。早期 Hooks 教程几乎都停在两个事件上:
PreToolUse:某条工具调用执行之前,用正则或脚本决定放行还是拦截(exit code 2 即阻断)。PostToolUse:某条工具调用执行之后,做格式化、日志、再校验。这套"双钩护栏"对付"防止 rm -rf"这类单条高危命令非常有效。但一旦任务进入批量与并行,它就暴露三个盲区:
Bash、Edit。每条单独看都合规:一条 git add .、一条 git commit、一条 git push -f。逐条过 PreToolUse 都不会拦——但拼起来就是一次覆盖远程的强推。单条钩子看不到"组合"。这三个盲区恰好对应三个新机制:组合盲区 → PostToolBatch(一批工具调用全部解析后、下一轮模型调用之前的唯一聚合点);噪声盲区 → async hook(把不紧急的检查丢到后台);语义盲区 → agent-based hook(让一个迷你 agent 做判断)。下面的实战就是围绕这三者展开。
-p 模式如何"喂料"无头模式的核心是 stdin 即输入、stdout 即输出,让它能像 grep、sed 一样塞进 Unix 管道。
最朴素的喂法是把内容 pipe 进去,结果重定向出来:
cat build-error.txt | claude -p 'concisely explain the root cause' > cause.txt
要点有三:
git diff 直接 pipe 进去,Claude 就不需要 Bash 权限去读 diff——这一点在锁权限的 CI 里极其重要。--output-format json,返回体里有 result(正文)、session_id,以及 total_cost_usd 和按模型的成本拆分。批量跑时靠它记账,不必再去后台查用量。--bare:可复现的批量前提裸跑批量的第一条铁律:用 --bare。
不加 --bare,claude -p 会加载交互式会话的全部上下文——你或同事 ~/.claude 里的某个 hook、项目 .mcp.json 里的某个 MCP 服务器,都会被捎带执行。结果就是"我本机能跑、CI 上结果不一样"。
claude --bare -p "Summarize this file" --allowedTools "Read"
--bare 跳过 hooks、skills、plugins、MCP、auto memory、CLAUDE.md 的自动发现,只认你显式传的 flag。需要什么就用 flag 显式带进来:--append-system-prompt-file、--settings、--mcp-config、--agents、--plugin-dir。官方明确:--bare 是脚本与 SDK 调用的推荐模式,未来会成为 -p 的默认。批量流水线里它等价于"确定性保证"。
批量任务的产物必须是机器可解析的。两种姿势:
纯 JSON 回收正文:
claude -p "Summarize this project" --output-format json | jq -r '.result'
强 schema 约束:用 --json-schema 让输出落在 structured_output 字段,字段名和类型完全由你定义:
claude -p "Extract main function names from auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
| jq '.structured_output'
把"一个 diff → 一次审查"封装成一次 -p 调用,循环遍历,每行一条结果写进 .jsonl,后续用 jq 聚合:
#!/usr/bin/env bash
set -euo pipefail
# 遍历当前分支相对 main 的每个已改文件,逐个喂给 -p
mapfile -t files < <(git diff --name-only main)
for f in "${files[@]}"; do
[ -f "$f" ] || continue
git diff main -- "$f" | claude --bare -p \
--append-system-prompt "你是安全审查员,只报告真实风险,按 schema 输出" \
--output-format json \
--json-schema '{"type":"object","properties":{"risk":{"type":"string","enum":["high","medium","low","none"]},"issues":{"type":"array","items":{"type":"string"}}},"required":["risk","issues"]}' \
| jq -c '{file: "'"$f"'", result: .structured_output, cost: .total_cost_usd}' \
>> reviews.jsonl
done
# 汇总高危项
jq -c 'select(.result.risk=="high")' reviews.jsonl
这条循环本身已经是一条流水线。但它的审查全靠 prompt 里的"安全审查员"人设,没有确定性护栏。如果 -p 内部的 agent 决定顺手 git commit 或删个文件,循环就翻了车。这就是为什么要在喂料之外,再加第二、第三件套。
常见问题
| 现象 | 原因 | 解决 |
|---|---|---|
claude -p 卡住不退出 |
起了后台 dev server / watch 进程 | v2.1.163+ 后 5 秒宽限即终止;subagent/workflow 豁免但默认 10 分钟封顶,用 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 调 |
| stdin 报错非零退出 | 输入超 10MB | 写盘后 prompt 里引用路径,别 pipe |
| CI 上结果与本地不一致 | 没用 --bare,捎带了本地配置 |
批量一律 --bare + 显式 flag |
structured_output 为空 |
没同时给 --output-format json |
schema 必须配 json 输出格式 |
Hooks 事件按节奏分三档:
SessionStart / SessionEndUserPromptSubmit / Stop / StopFailurePreToolUse / PostToolUsePostToolBatch 是第三档里的特殊存在:它在一整批并行工具调用全部解析完毕、下一轮模型推理开始之前触发。这是你能在"一批改动"这个粒度上做判断的唯一时机——既看得到这批每条调用的输入与结果,又能赶在模型继续推理前拦下整批。
它不支持 matcher,永远每次都触发(与 UserPromptSubmit、Stop、TaskCreated/Completed 等同列)。配置直接挂在事件名下:
{
"hooks": {
"PostToolBatch": [
{
"hooks": [
{ "type": "command", "command": ".claude/hooks/guard-batch.sh" }
]
}
]
}
}
handler 类型有四类,恰好覆盖从"确定性"到"概率性"的频谱:command(shell)、http(POST 端点)、prompt(用 LLM 判条件)、agent(用迷你 agent 判)。
钩子通过两种方式回流决策:
{"decision":"block","reason":"..."}、或为某些事件注入额外上下文。下面这个钩子专门抓"单条合规、组合危险"的情形——一批里同时出现 git commit 与 git push -f:
#!/usr/bin/env bash
# .claude/hooks/guard-batch.sh
input="$(cat)"
# PostToolBatch 的 input 包含本批所有工具调用的输入与结果(字段以官方 reference 为准)
cmds=$(printf '%s' "$input" | jq -r '.. | .command? // empty' | tr '\n' '|')
has_commit=$(printf '%s' "$cmds" | grep -c 'git commit' || true)
has_force=$(printf '%s' "$cmds" | grep -c 'push.*-f\|--force' || true)
if [ "$has_commit" -gt 0 ] && [ "$has_force" -gt 0 ]; then
jq -nc '{decision:"block", reason:"同批出现 commit + push -f,疑似覆盖远程,已拦截整批"}' >&2
exit 2
fi
放进 .claude/settings.json 后,无论 -p 还是交互模式,只要一轮里这两条命令同时出现,整批就被拦在下一轮推理之前。这是单条 PreToolUse 永远做不到的。
确定性钩子(exit code 2、正则)快、便宜、可复现,但只懂模式不懂语义。"这批改动是否在语义上像硬编码了凭据",正则会漏掉 "AKIA" 开头被切片拼接 这种变体。
agent-based hook 让你用一个迷你 Claude agent来判这件事。它接收钩子输入,跑一次轻量推理,按 schema 返回判定。配置上挂在同一事件名下,type 取 agent 类(具体字段以官方 "agent hook configuration" 为准)。配合 PostToolBatch,等于在"聚合点"上叠加一个"概率判断层":
{
"hooks": {
"PostToolBatch": [
{ "hooks": [
{ "type": "command", "command": ".claude/hooks/guard-batch.sh" } // 确定性:组合序列
]},
{ "hooks": [
{ "type": "agent", /* model/prompt/schema 见官方字段 */ } // 概率性:语义风险
]}
]
}
}
分工原则:确定能写成正则的,绝不上模型;只有语义判断才交给 agent。这样既保住批量速度,又补上语义盲区。判定结果回流为 decision,命中则阻断,未命中则放行。
文件改动后想跑测试,又不想让 PostToolUse 每条都阻塞主循环——这就是 async hook 的场景。它把钩子丢到后台执行,主代理循环继续推进,结果异步回流(典型用于"跑测试/发通知/写日志"这类不必立即阻塞的事)。
async hook 的输出不会像同步钩子那样即时回流进当前推理,因此不要用它做必须当场阻断的检查。需要阻断的(组合拦截、语义拦截)仍走同步 PostToolBatch + exit code 2。async 适合"事后核验":测试挂了就再触发一轮修复,而不是卡住当前批。
限制也要记牢:后台任务在 -p 退出时有 5 秒宽限;subagent/workflow 豁免该宽限但默认 10 分钟封顶。所以 async 测试要么自包含、要么走子 agent。
目标:对当前分支相对 main 的全部改动,自动跑一遍"确定性护栏 + 语义护栏 + 后台测试"。
目录结构
.claude/
├── settings.json # 注册 PostToolBatch 钩子(command + agent)
├── hooks/
│ └── guard-batch.sh # 确定性:组合序列拦截
└── prompts/
└── secret-review.md # 语义:硬编码凭据审查(供 agent hook 用)
.claude/settings.json
{
"hooks": {
"PostToolBatch": [
{ "hooks": [
{ "type": "command", "command": ".claude/hooks/guard-batch.sh" }
]},
{ "hooks": [
{
"type": "agent",
"prompt": "审查本批所有 Bash/Edit 改动是否引入硬编码凭据(API key、token、私钥)。仅当确信时返回 block。",
"model": "haiku"
}
]}
],
"FileChanged": [
{ "matcher": "*.py", "hooks": [
{ "type": "command", "command": "pytest -q &", "_note": "async:后台跑测试,不阻塞" }
]}
]
}
}
入口脚本 batch-review.sh
#!/usr/bin/env bash
set -euo pipefail
: > reviews.jsonl
mapfile -t files < <(git diff --name-only main)
for f in "${files[@]}"; do
[ -f "$f" ] || continue
# --bare 保证 CI 可复现;--permission-mode 锁住可写范围
git diff main -- "$f" | claude --bare -p \
--append-system-prompt-file .claude/prompts/secret-review.md \
--permission-mode acceptEdits \
--allowedTools "Read,Edit,Bash(git diff *),Bash(git add *),Bash(git commit *)" \
--output-format json \
--json-schema '{"type":"object","properties":{"risk":{"type":"string"},"issues":{"type":"array","items":{"type":"string"}}},"required":["risk","issues"]}' \
| jq -c '{file:"'"$f"'", r:.structured_output, cost:.total_cost_usd}' \
>> reviews.jsonl
done
echo "== 高危 =="; jq -c 'select(.r.risk=="high")' reviews.jsonl
echo "== 总成本 =="; jq '[.cost] | add' reviews.jsonl
运行效果:每个文件一次 -p;PostToolBatch 的 command 钩子拦组合危险序列、agent 钩子判语义密钥风险、FileChanged 的 async 钩子在后台对改动过的 .py 跑 pytest。最终 reviews.jsonl 一行一文件,可被 jq 聚合成高危清单与总成本。整条链路无人值守、可复现、有护栏、可记账。
flowchart TD
A[git diff main 文件清单] --> B{遍历每个文件}
B --> C[git diff 单文件 管道喂料]
C --> D["claude --bare -p\n--permission-mode acceptEdits\n--json-schema 结构化输出"]
D --> E[agent 循环]
E -->|每条工具调用| F[PreToolUse/PostToolUse 单条护栏]
F --> G{"一批并行调用落定\nPostToolBatch 触发"}
G -->|command 钩子| H[组合序列拦截 commit+push -f]
G -->|agent 钩子| I[语义判断 硬编码凭据]
H -->|命中| J[exit 2 阻断整批]
I -->|命中| J
H -->|放行| K[下一轮模型推理]
I -->|放行| K
E -->|文件落盘| L[FileChanged async 钩子]
L -.后台非阻塞.-> M[pytest 测试]
K --> N[Stop 输出结构化 JSON]
N --> O[jq 聚合进 reviews.jsonl]
O --> B
/hooks 看 PostToolBatch 计数是否 > 0;确认没有写 matcher(写了会被静默忽略,但不会报错,最容易骗人)。haiku,且只在 PostToolBatch(每批一次)触发,不要挂在 PostToolUse(每条一次)。-p 适合几十个文件;上百个时改成按目录分批,或用 Subagent 并行派活(每个 subagent 独立上下文窗口,不互相污染),主会话只做汇总。--bare 是头号元凶。养成"批量即 bare"的肌肉记忆。-p 单任务、PreToolUse/PostToolUse 基础,否则"聚合点"的动机讲不透。可作为进阶周课程。commit+push -f)是最能打动工程负责人的演示点,5 分钟 demo 即可建立价值认知。-p 与 --bare 模式、管道喂料、--json-schema 结构化输出、stdin 上限与后台任务宽限期。更多 Claude Code 实战内容,欢迎关注公众号「51学通信」,联系站长爱卫生(微信:gprshome201101)交流。
[!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 等场景全覆盖。
