本教程带你用 Claude Code 的 -p 无头模式构建一支定时巡检舰队:cron 按计划触发,多个 Agent 并行扫描代码库的不同健康维度(安全、技术债、性能、依赖、覆盖),每个 Agent 以 stream-json / json 流式结构化输出,最后由一个聚合 Agent 把散落的 JSON 合并成一份统一健康报告并做日环比。适合已掌握交互式 Claude Code、想把 Agent 从「随用随起」升级为「无人值守定期巡逻」的后端工程师与 DevOps 人员。
claude --version 可输出版本号)jq(结构化解析必备,sudo apt install jq 或 brew install jq)ANTHROPIC_API_KEY 环境变量,或 claude setup-token 生成的长期 OAuth token(cron 无法弹出浏览器登录)交互式 Claude Code 给你的是彩色 TUI;无头模式下,你拿到的是可被程序解析的数据流。--output-format 有三个取值,理解它们的差异决定了你的脚本能否稳定工作:
| 取值 | 输出形态 | 适用场景 | 解析难度 |
|---|---|---|---|
text(默认) |
纯文本最终回复 | 人工阅读、简单管道 | 低(但有围栏/噪声) |
json |
单个 JSON 对象,含 result、cost_usd、duration_ms、num_turns、session_id |
一次性任务、要成本/耗时统计 | 低 |
stream-json |
换行分隔的多个 JSON 事件(每条消息一个对象) | 实时监控、自定义 UI、抽取中间工具调用 | 中(需 jq 过滤事件类型) |
关键事实:stream-json 是「每个事件一个 JSON 对象、逐行输出」的流,事件类型包括 system(会话初始化)、assistant(含文本与 tool_use)、user(tool_result)、以及最终的 result。这意味着你能在 Agent 思考的同时就抓到它读了哪些文件、跑了什么命令——这正是「巡检舰队」实时监控的基础。
# 看一眼 json 模式返回的结构
claude -p "用一句话总结当前目录是什么项目" --output-format json | jq .
预期结果:输出一个对象,关键字段形如:
{
"type": "result",
"subtype": "success",
"is_error": false,
"duration_ms": 4231,
"num_turns": 2,
"cost_usd": 0.0123,
"result": "这是一个用 Node.js 编写的 TODO CLI 工具项目……",
"session_id": "a1b2c3d4-..."
}
常见问题:
--output-format requires --verbose?部分版本对 stream-json 要求同时加 --verbose 才会吐出完整事件流。加上即可:--output-format stream-json --verbose。json 模式拿到的 result 里混了 markdown 围栏?这是模型把 JSON 包进 ```json ... ``` 导致的,下一步专门解决。jq 从 stream-json 精准抽取结果巡检舰队的每个「哨兵」都要输出机器可读的结果。先掌握从流里捞「最终结论」与「工具调用记录」的固定写法:
# 只取最终 result 事件
claude -p "列出本仓库的入口文件,用一句话回答" \
--output-format stream-json --verbose \
| jq -c 'select(.type=="result")'
# 取整个过程中 Agent 读取过的文件(来自 tool_use 事件)
claude -p "浏览 src 目录结构" \
--output-format stream-json --verbose \
| jq -r 'select(.type=="assistant")
| .message.content[]?
| select(.type=="tool_use" and .name=="Read")
| .input.file_path'
预期结果:第一条命令打印一行压缩 JSON(含成本与耗时);第二条逐行打印 Agent 实际打开的每个文件路径。后者对「审计 Agent 到底看了什么」极有价值——巡检报告里附上「本次扫描读取的文件清单」能显著提升可信度。
常见问题:流式输出在管道下游被缓冲、jq 收不到数据。解决:用 stdbuf -oL 或 unbuffer 关闭缓冲——stdbuf -oL claude -p ... --output-format stream-json --verbose | jq ...。
聚合的前提是每个哨兵的输出结构一致。靠 --append-system-prompt 注入硬约束,并在出口处剥离可能的 markdown 围栏:
#!/usr/bin/env bash
# run-one.sh <dimension> —— 跑单个维度的哨兵,输出干净 JSON
set -euo pipefail
DIM="$1"
TARGET_REPO="${2:-$HOME/projects/sample-repo}"
SYS='你是代码健康巡检哨兵。只输出一个合法 JSON 对象,禁止任何 markdown 围栏、禁止任何散文解释。JSON schema 固定为:
{"dimension": string, "score": number(0-100), "severity": "high"|"medium"|"low", "findings": [{"file": string, "issue": string, "suggestion": string}], "summary": string}'
RAW=$(claude -p "维度=$DIM。扫描仓库 $TARGET_REPO,按 schema 输出 JSON。" \
--output-format json \
--append-system-prompt "$SYS" \
--max-turns 6 \
--allowedTools "Read,Grep,Glob" \
| jq -r '.result')
# 防御性:剥离可能的 ```json 围栏后再次校验
echo "$RAW" | sed 's/^```json//I; s/^```//; s/```$//' | jq .
四个细节决定了这段的健壮性:
--max-turns 6:给巡检哨兵一个回合上限,防止它在超大仓库里无限读文件把预算烧光。这是舰队自动化的成本护栏。--allowedTools "Read,Grep,Glob":把哨兵锁定为只读。巡检不该改代码,白名单从根上杜绝了误删文件。--append-system-prompt:不改 Agent 底层行为,只叠加一条「输出契约」,比改系统提示更安全、可复用。sed 剥围栏 + jq .:双重保险。若模型仍偷偷加围栏,sed 削掉;若仍非法 JSON,jq . 会非零退出,被 set -e 捕获。预期结果:脚本退出码 0,stdout 是一个被 jq 美化、结构合法的 JSON 对象。
常见问题:
jq: parse error:模型没遵守契约。对策:把 schema 再写细、把「禁止围栏」移到 --append-system-prompt 最前面;或对 result 先 jq -R 取原文、用 sed/awk 截取首个 { 到末个 }。set -e 一刀切。五个维度串行跑会浪费等待时间。用 xargs -P 做并发扇出,每个维度一个进程:
#!/usr/bin/env bash
# fan-out.sh —— 并行派出五个哨兵
set -uo pipefail # 注意:去掉 -e,让单哨兵失败不连累全局
DATE=$(date +%Y%m%d-%H%M)
OUTDIR="reports/$DATE"
mkdir -p "$OUTDIR"
DIMENSIONS="security techdebt performance dependencies coverage"
TARGET="$HOME/projects/sample-repo"
echo "$DIMENSIONS" | tr ' ' '\n' \
| xargs -P 5 -I {} bash -c '
dim="{}"
./run-one.sh "$dim" "'"$TARGET"'" > "'"$OUTDIR"'/$dim.json" 2> "'"$OUTDIR"'/$dim.err"
echo "[done] $dim -> $?"
'
echo "--- 扇出完成,成功 $(ls "$OUTDIR"/*.json 2>/dev/null | wc -l) 份 ---"
预期结果:约等于「最慢那个哨兵」的耗时(而非五个之和)。reports/20260710-0217/ 下出现 security.json、techdebt.json 等文件,每个都是合法 JSON。
常见问题:
-P 5 降到 -P 3,或在 run-one.sh 顶部加 sleep $((RANDOM % 5)) 错峰。.json 是空文件:因为去掉了 set -e,崩的那份 .err 里有原因、其它四份照常产出。下一步聚合时跳过非法文件即可。交互登录靠浏览器 OAuth,cron 里没人点「允许」。两条路:
# 路线 A:API Key(CI/调度最省事)
export ANTHROPIC_API_KEY="sk-ant-..."
# 写进 ~/.bashrc 不够——cron 环境极简,要在 crontab 顶部或脚本内显式 export
# 路线 B:长期 OAuth token
claude setup-token # 交互式跑一次,输出一行 token
# 然后 export CLAUDE_CODE_OAUTH_TOKEN="那串token"
cron 的两个工程要点:避开整点(所有定时任务挤在 0 2 * * * 会撞车)和显式补全环境(cron 的 PATH 往往不含 node/claude):
# crontab -e
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin:/home/gprshome/.npm-global/bin
ANTHROPIC_API_KEY=sk-ant-xxxxxxxx
# 每天凌晨 2:17 跑巡检(错峰,非整点)
17 2 * * * cd /home/gprshome/patrol-fleet && ./run-patrol.sh >> logs/cron.log 2>&1
# 每周一 7:23 跑一次「依赖升级评估」长任务
23 7 * * 1 cd /home/gprshome/patrol-fleet && ./run-one.sh dependencies >> logs/deps-weekly.log 2>&1
run-patrol.sh 是舰队总指挥,串起扇出与聚合:
#!/usr/bin/env bash
# run-patrol.sh
set -uo pipefail
cd "$(dirname "$0")"
DATE=$(date +%Y%m%d-%H%M)
echo "[$(date -Iseconds)] 巡检开始 run=$DATE"
./fan-out.sh "$DATE"
./aggregate.sh "$DATE"
echo "[$(date -Iseconds)] 巡检结束 run=$DATE"
预期结果:次日凌晨 2:17,logs/cron.log 出现「巡检开始/结束」记录,reports/ 多一个带时间戳的目录。
常见问题:
PATH 问题,claude/node 找不到。先在终端 which claude 拿绝对路径,写进 crontab 的 PATH 或脚本里用绝对路径调用。401:ANTHROPIC_API_KEY 没被 cron 读到。确认它写在 crontab 顶部或 run-patrol.sh 内部 export,而非只写在 ~/.bashrc(cron 不加载它)。五个哨兵各自只看一个面,需要有人「俯瞰全局」。再用一个 -p Agent 读全部 JSON、产出统一报告:
#!/usr/bin/env bash
# aggregate.sh <DATE>
set -euo pipefail
DATE="$1"
OUTDIR="reports/$DATE"
# 合并所有合法 JSON 为一个数组,跳过损坏的(容错聚合)
jq -s '[.[] | select(.dimension != null)]' "$OUTDIR"/*.json > "$OUTDIR/_merged.json"
claude -p "$(cat <<EOF
你是代码健康总指挥。下面是 N 个维度哨兵的 JSON 汇总(_merged.json 内容):
$(cat "$OUTDIR/_merged.json")
请输出一份 Markdown 巡检报告,必须包含:1) 总体健康分(各维度 score 加权);2) 按 severity 倒序排列的高危问题清单(标注文件与建议);3) 与上一份报告 reports/ 目录中最新旧报告的「日环比」变化(新增/已解决);4) 一段给团队负责人的三句话结论。
EOF
)" \
--append-system-prompt "报告用简体中文,务实,不要客套。" \
--max-turns 4 \
--allowedTools "Read" \
> "$OUTDIR/REPORT.md"
echo "报告已生成:$OUTDIR/REPORT.md"
注意聚合 Agent 的 --allowedTools "Read":它只需要读旧报告做日环比,不需要写、不需要 grep 全仓,权限收得越紧越安全。
预期结果:reports/20260710-0217/REPORT.md 是一份带总体分、高危清单、日环比、结论的完整报告,可直接进群推送或转 PDF。
常见问题:
reports/ 里没有历史,Agent 会如实写「无历史可比」。从第二天起才有环比。_merged.json 因某份损坏 JSON 导致 jq -s 报错:fan-out.sh 已保证每份都过 jq . 校验才会落盘,损坏的不会进目录,故安全。把上面六步组装成一个可克隆即用的项目。目录结构:
patrol-fleet/
├── run-patrol.sh # 总指挥:扇出 → 聚合
├── fan-out.sh # 并行派出哨兵
├── run-one.sh # 单哨兵:输出干净 JSON
├── aggregate.sh # 聚合 Agent:出 REPORT.md
├── dimensions/ # 每个维度的 prompt 模板(可选,让 run-one 读它)
│ ├── security.md
│ ├── techdebt.md
│ ├── performance.md
│ ├── dependencies.md
│ └── coverage.md
├── crontab # cron 配置样例
├── logs/ # cron.log / 各哨兵 err
└── reports/ # 按时间戳归档:REPORT.md + 各维度 json
dimensions/security.md 示例(让哨兵更聚焦,比靠系统提示更可维护):
你是安全哨兵。重点检查:
- 硬编码的密钥、token、密码(含 .env 误提交)
- 危险函数:eval、exec、反序列化、SSRF 入口
- SQL 拼接、命令拼接
- 依赖里的已知 CVE(若能从 lockfile 推断)
按既定 JSON schema 输出,severity 严格分级。
把 run-one.sh 改成读取模板:PROMPT_BODY=$(cat "dimensions/$DIM.md"),再把 $PROMPT_BODY 拼进 -p。这样新增一个巡检维度只需丢一个 .md 文件,零改代码。
一键验证(不依赖 cron,先手动跑通):
cd ~/patrol-fleet
chmod +x *.sh
./run-patrol.sh # 立刻跑一次,确认 reports/ 下产出 REPORT.md
cat reports/$(ls reports/ | tail -1)/REPORT.md
跑通后再 crontab crontab 装上定时,舰队就正式值班了。
进阶扩展:
run-patrol.sh 末尾加 curl 把 REPORT.md 发到飞书/钉钉/Slack webhook,实现「早上一条巡检播报」。run-one.sh 套一层 retry(最多 2 次),提升健壮性。jq 每天抽出总体分写进 trend.csv,配 gnuplot 或丢给可视化 MCP 画健康分走势线。severity==high 数量超阈值,用另一个 -p Agent 起草「立即处置工单」并 @ 负责人。flowchart TD
CRON["cron 定时触发<br/>每日 02:17"] --> RUN["run-patrol.sh 总指挥"]
RUN --> FAN["fan-out.sh 并行扇出<br/>xargs -P 5"]
FAN --> S1["security 哨兵<br/>--allowedTools Read,Grep,Glob"]
FAN --> S2["techdebt 哨兵"]
FAN --> S3["performance 哨兵"]
FAN --> S4["dependencies 哨兵"]
FAN --> S5["coverage 哨兵"]
S1 --> J1["security.json<br/>--output-format json"]
S2 --> J2["techdebt.json"]
S3 --> J3["performance.json"]
S4 --> J4["dependencies.json"]
S5 --> J5["coverage.json"]
J1 & J2 & J3 & J4 & J5 --> MERGE["jq -s 合并<br/>跳过损坏文件"]
MERGE --> AGG["聚合 Agent<br/>读旧报告做日环比"]
AGG --> REP[("REPORT.md<br/>总体分+高危+环比+结论")]
REP --> PUSH["webhook 推送群消息"]
style CRON fill:#fde68a
style REP fill:#bbf7d0
style AGG fill:#bfdbfe
code.claude.com/docs/en/headless —— -p、--output-format、SDK 编程调用的权威说明。code.claude.com/docs/en/cli-reference —— 全部 flag(--max-turns、--allowedTools、--resume、--permission-mode)逐条解释。backgroundclaude.com/blog/stream-json —— 专门讲 stream-json 第三种输出格式的事件结构与解析技巧。jq 解析;第 2 课时讲并行扇出、cron、聚合;实操课要求学员在自己的仓库上跑通一支五维度舰队并产出 REPORT.md。text/json/stream-json 差异;能用 --allowedTools 把哨兵锁成只读;能在 cron 里成功触发一次非交互巡检并拿到结构化报告。_merged.json 的现成片段降低门槛。-p/输出格式的权威文档--output-format、--max-turns、--allowedTools、--append-system-prompt)逐条说明stream-json 换行分隔事件结构与 jq 解析技巧jq 解析、CLI-vs-SDK 决策与真实 shell 片段--print --output-format 绕过 TUI 的完整实践指南[!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 等场景全覆盖。
