学习目标:掌握 Claude Code 子代理(Subagent)从定义到编排的完整链路,学会在
-p无头模式下批量派发专项任务、用stream-json实时追踪进度,并给子代理挂载专属 Hooks,最终落地一个"多仓库多角色代码体检流水线"。适合已能用claude -p跑通单条命令、想进一步构建无人值守批量自动化与多角色协作的开发者。
--json-schema、--bare 等能力),并能用 claude -p "hello" 跑通一次无头调用。jq、bash(或 zsh)、git;Windows 用户建议在 WSL 或 Git Bash 下操作。git clone 两三个小仓库)用于批量演练。-p(--print)无头模式让 Claude Code 变成一条可被管道、脚本、CI 调用的命令;而子代理(Subagent)则是"带独立上下文窗口、独立工具集、独立权限、独立模型"的专项工人。把两者结合,可以做到三件单用 -p 做不好的事:
haiku,需要推理的用 sonnet,按角色挑模型,把钱花在刀刃上。.claude/agents/ 下一个 Markdown 文件,可提交版本控制、随插件分发,团队和 CI 共用同一份"专家配置"。整套协作模型如下:
flowchart LR
subgraph 驱动层["驱动层(Shell / CI)"]
SH["run-audit.sh<br/>批量派活循环"]
end
subgraph 无头层["无头层 -p"]
MAIN["claude -p<br/>主编排会话"]
SA1["子代理: security-scanner<br/>model=haiku 只读"]
SA2["子代理: complexity-audit<br/>model=sonnet 只读"]
SA3["子代理: test-coverage-advisor<br/>model=sonnet 只读"]
end
subgraph 沉淀层["沉淀层"]
JSON["每仓 JSON 结果"]
DASH["聚合仪表盘"]
end
SH -->|"逐仓/逐角色"| MAIN
MAIN -->|"Agent 工具委派"| SA1
MAIN -->|"Agent 工具委派"| SA2
MAIN -->|"Agent 工具委派"| SA3
SA1 --> JSON
SA2 --> JSON
SA3 --> JSON
JSON --> DASH
关键认知:子代理可以两种方式被启动——
-p 会话里,Claude 根据子代理的 description 自动委派,或你在提示里点名"使用 security-scanner 子代理",Claude 通过 Agent 工具调用它。--agent <name> 直接把某个子代理当作整场 -p 会话的唯一执行者,适合"一个角色跑完一个任务"的纯批量场景。子代理就是带 YAML frontmatter 的 Markdown 文件,存放在:
| 位置 | 作用域 | 优先级 |
|---|---|---|
~/.claude/agents/ |
你所有项目(个人) | 4(低) |
.claude/agents/ |
当前项目(可提交版本控制) | 3 |
--agents '<json>' |
仅本次会话(脚本/CI 临时) | 2 |
| 管理设置目录 | 组织级强制下发 | 1(高) |
先在示例项目里建目录与三个子代理。
security-scanner.md(用最快最便宜的 Haiku,只读):
---
name: security-scanner
description: 扫描硬编码密钥、SQL 注入、不安全反序列化等安全风险。在涉及安全审计、密钥扫描、漏洞排查时主动委派。
tools: Read, Grep, Glob
model: haiku
permissionMode: default
color: red
---
你是一名安全审计专家。给定代码库,只关注:
1. 硬编码密钥、token、密码、连接串
2. SQL 拼接、命令注入、路径穿越
3. 不安全的反序列化、弱加密算法
输出严格 JSON 数组,每项含 file、line、severity(critical/high/medium/low)、rule、evidence(原代码片段)、suggestion。
不要解释,不要修复,只输出 JSON 数组。
complexity-audit.md(需要推理,用 Sonnet):
---
name: complexity-audit
description: 定位圈复杂度过高的函数与重复代码块。在代码复杂度分析、技术债评估、重构候选筛选时委派。
tools: Read, Grep, Glob
model: sonnet
color: yellow
---
你是代码复杂度分析专家。识别:
- 圈复杂度过高的函数(嵌套 if/for/while 过深)
- 超长函数(>80 行)
- 重复代码块(疑似可抽取)
输出 JSON 数组:file、function、issue_type(complexity/length/duplication)、detail、refactor_hint。
只输出 JSON,不要修复。
test-coverage-advisor.md:
---
name: test-coverage-advisor
description: 找出缺少测试的关键模块并给出测试用例建议。在测试覆盖分析、补测试任务规划时委派。
tools: Read, Grep, Glob
model: sonnet
color: green
---
你是测试设计专家。找出高风险却缺测试的模块(核心业务逻辑、边界条件、错误处理路径)。
输出 JSON 数组:file、module、risk(missing/partial/edge)、recommended_tests(数组,每条一句)。
只输出 JSON。
预期结果:三个文件就位后,在交互模式里运行
/agents,能在 Library 标签页看到这三个自定义子代理。文件直接落盘需要重启会话才加载;用/agents界面创建的则即时生效。常见问题 - 子代理没被自动触发? 90% 是
description写得太宽泛。description 是 Claude 判断"何时委派"的唯一依据,要写清"在什么场景下、做什么事",并鼓励"主动委派"。 - 同目录下两个文件name相同? Claude Code 只保留一个、静默丢弃另一个,不给警告。务必保证name在整棵子目录树里唯一。 - 想让某子代理绝对不能写文件? 用tools白名单(只列 Read/Grep/Glob)比用disallowedTools黑名单更安全——白名单天然屏蔽未来新增的危险工具。
-p 单发验证子代理批量之前,先单发跑通。两种启动方式各试一遍。
# 在某个目标仓库根目录执行
claude -p "用 security-scanner、complexity-audit、test-coverage-advisor 三个子代理分别审计当前目录,\
把每个子代理的 JSON 结论原样拼接成一个大数组返回。" \
--output-format json \
--allowedTools "Read,Grep,Glob" \
| jq '.result'
主会话会通过 Agent 工具把任务拆给三个子代理,它们各自独立读文件、互不污染主上下文,最终把结论汇总到 .result。
claude -p "审计当前目录的安全风险" \
--agent security-scanner \
--output-format json \
| jq '.result'
--agent 让 security-scanner 直接成为这场 -p 会话的唯一执行者,跳过主编排层,开销更低,最适合"一仓一角色"的纯批量。
--output-format json 的 .result 仍是文本。要拿到强约束的结构化数据,叠加 --json-schema,结果落在 .structured_output:
claude -p "提取 auth.py 中所有顶层函数名" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
| jq '.structured_output.functions'
预期结果:
.structured_output严格符合你给的 schema,可直接喂给下游jq或聚合脚本,无需正则解析自由文本。常见问题 - 结果里混入了说明性文字,schema 解析失败? 子代理的系统提示里务必明确写"只输出 JSON,不要解释"。子代理只收到它的系统提示,不会继承 Claude Code 主提示,约束必须写进 body。 - 被权限弹窗卡住?
-p模式不能交互授权,未预批准的工具一旦触发会直接中止。用--allowedTools显式放行所需工具,或--permission-mode acceptEdits。
核心思路:用一个 shell 循环把"仓库列表 × 角色"展开成一组 claude -p 调用,用 xargs -P 并发,每条调用产出一份 JSON,最后 jq 聚合。
run-audit.sh:
#!/usr/bin/env bash
set -euo pipefail
# 待审计仓库列表(每行一个路径)
REPOS=(./repos/auth-svc ./repos/payment-svc ./repos/notification-svc)
# 角色与对应子代理
ROLES=(security-scanner complexity-audit test-coverage-advisor)
OUTDIR="./audit-out"
mkdir -p "$OUTDIR"
audit_one() {
local repo="$1" role="$2"
# 用仓库短名 + 角色 + 时间戳命名,避免覆盖
local name; name="$(basename "$repo")"
claude -p "审计 $repo 目录,只输出 JSON 结论。" \
--agent "$role" \
--output-format json \
--allowedTools "Read,Grep,Glob" \
> "$OUTDIR/${name}__${role}.json"
echo "✓ $name :: $role"
}
export -f audit_one
export OUTDIR
# 展开 仓库×角色 笛卡尔积,并发度 4
for r in "${REPOS[@]}"; do for role in "${ROLES[@]}"; do echo "$r|$role"; done; done \
| awk -F'|' '{print $1"|"($2-1)}' \
| tr '|' ' ' \
| xargs -n2 -P4 bash -c 'audit_one "$0" "$1"'
echo "全部完成,结果在 $OUTDIR/"
说明:
--agent "$role"让每个角色作为独立主代理跑,互不干扰;-P4控制并发度,避免一次性打满 API 限流。--bare在 CI 里推荐加上,以屏蔽队友~/.claude里的 hook/MCP 干扰,保证多机一致。
# 把所有单仓 JSON 的 structured_output/result 汇总,附 cost
jq -s 'map({
repo: input_filename | ltrimstr("./audit-out/") | split("__")[0],
role: input_filename | ltrimstr("./audit-out/") | split("__")[1] | rtrimstr(".json"),
cost: .total_cost_usd,
result: .result
})' "$OUTDIR"/*.json > ./audit-report.json
# 统计总花费
jq '[.[].cost] | add' ./audit-report.json
--output-format json 的响应里自带 total_cost_usd 和按模型拆分的费用明细,批量后可精确核算每个仓库、每个角色花了多少钱。
常见问题 - 并发太高被限流?
stream-json里会发出system/api_retry事件;若用 json 模式则表现为调用失败。调低-P,或对 429 做指数退避重试。 - 后台进程把-p卡住不退出? v2.1.163 之后,-p结束约 5 秒会终止其启动的后台 shell;后台子代理/工作流的等待默认上限 10 分钟(CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS可调)。 - 想跨多机/CI 完全一致? 加--bare,并用--agents '<json>'把子代理定义内联进命令,不依赖磁盘文件。下一节会演示这种内联写法。
Hooks 让你在生命周期的关键节点自动执行 shell 命令。和子代理结合时有两个要点:
hooks 字段:在子代理 frontmatter 里写 hooks:,该 hook 只在此子代理生命周期内触发(注意:来自插件的子代理会忽略此字段)。agent_type 字段:项目级 hook 收到的输入 JSON 里带 agent_type,值就是当前子代理的 name,据此可区分"是哪个子代理触发的"。在 .claude/settings.json 里:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Agent",
"hooks": [
{
"type": "command",
"command": "jq -r '\"[\\(now|todateiso8601)] agent=\\(.tool_response.agent_type // \"main\") task=\\(.tool_input.description // \"-\")\"' >> ./.audit-delegation.log"
}
]
}
]
}
}
每次主会话通过 Agent 工具委派子代理,都会追加一行审计日志,agent_type 标明派给了谁——批量跑完你能复盘"哪个角色被调了多少次"。
在 security-scanner.md 里追加(让安全子代理每次跑完都落一份痕迹):
---
name: security-scanner
description: 扫描硬编码密钥与注入风险,安全审计时主动委派。
tools: Read, Grep, Glob
model: haiku
color: red
hooks:
Stop:
- hooks:
- type: command
command: "echo \"security-scancer finished at $(date)\" >> ./.security-runs.log"
---
...(同前系统提示)
Stop 事件在该子代理结束本轮时触发。可改成调用 webhook、推送 IM 通知、触发下游流水线。
批量派活时,可以让一个 UserPromptSubmit hook 自动把仓库元信息(git 分支、最近 commit、语言统计)注入每条提示,省去每次手写:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "jq -n --arg br \"$(git rev-parse --abbrev-ref HEAD)\" --arg sha \"$(git rev-parse --short HEAD)\" '{additionalContext: (\"当前分支: \" + $br + \",HEAD: \" + $sha)}'"
}
]
}
]
}
}
hook 返回的 additionalContext 会被追加到用户提示后,子代理据此知道自己在审计哪个快照。
注意:
--bare模式不读取任何 hook/MCP/CLAUDE.md,所以上面这些 hook 在--bare下不会生效。CI 里若要用 hook,就别加--bare;若要绝对可复现,就用--bare并把逻辑前移到脚本里。两者择一,别混用。
批量跑几十个仓库时,等所有 JSON 落盘再看结果太被动。用 stream-json 实时拿到事件流,边跑边看进度。
claude -p "审计当前目录安全风险" \
--agent security-scanner \
--output-format stream-json \
--verbose \
--include-partial-messages \
| jq -rc 'select(.type=="system" and .subtype=="init") | "会话启动,模型=\(.model)"'
关键事件类型:
system/init:会话首个事件,含 model、加载的工具、MCP 服务器、plugins 与 plugin_errors——CI 里可用它检测"插件没加载上就 fail"。stream_event + event.delta.type=="text_delta":逐 token 文本,可拼成实时打字效果。system/api_retry:API 重试前发出,含 attempt、max_retries、retry_delay_ms、error_status——可据此自绘重试进度或自定义退避。result:最终结果,含 total_cost_usd。把驱动脚本改用 stream-json,再用 jq 过滤出"开始/重试/完成"事件,就能拼出一个轻量进度看板:
... | jq -rc '
if .type=="system" and .subtype=="init" then "▶ 启动"
elif .type=="system" and .subtype=="api_retry" then "↻ 重试 #\(.attempt)"
elif .type=="result" then "■ 完成,花费 $\(.total_cost_usd)"
else empty end'
把前六步拼成一个真实可用的小项目。目录结构:
code-health-pipeline/
├── .claude/
│ ├── agents/
│ │ ├── security-scanner.md
│ │ ├── complexity-audit.md
│ │ └── test-coverage-advisor.md
│ └── settings.json # 委派日志 + 上下文注入 hooks
├── repos/ # 待审计仓库(git clone 进来)
├── run-audit.sh # 批量派活驱动
├── aggregate.sh # jq 聚合
└── audit-out/ # 产出(gitignore)
核心驱动(内联子代理版本,CI 友好,不依赖磁盘文件):
#!/usr/bin/env bash
# run-audit.sh —— 用 --agents 内联定义,--bare 保证多机一致
set -euo pipefail
AGENTS_JSON='{"security-scanner":{"description":"扫描密钥与注入风险,安全审计时委派","prompt":"只输出 JSON 数组:file,line,severity,rule,evidence,suggestion","tools":["Read","Grep","Glob"],"model":"haiku"}}'
REPOS=(./repos/auth-svc ./repos/payment-svc)
run_one() {
local repo="$1"
local name; name="$(basename "$repo")"
claude --bare -p "审计 $repo 目录的安全风险。" \
--agents "$AGENTS_JSON" \
--agent security-scanner \
--output-format json \
--allowedTools "Read,Grep,Glob" \
> "./audit-out/${name}__security.json"
}
export -f run_one; export AGENTS_JSON
mkdir -p audit-out
printf '%s\n' "${REPOS[@]}" | xargs -n1 -P4 -I{} bash -c 'run_one "$@"' _ {}
聚合脚本 aggregate.sh:
#!/usr/bin/env bash
jq -s 'map({
repo: (input_filename | split("/") | last | split("__")[0]),
cost: .total_cost_usd,
findings: (.result | fromjson? // [])
}) | {total_cost: (map(.cost)|add), reports: .}' \
audit-out/*.json > audit-report.json
echo "总花费:$(jq '.total_cost' audit-report.json) USD"
echo "高危项数:$(jq '[.reports[].findings[]? | select(.severity=="critical" or .severity=="high")] | length' audit-report.json)"
执行效果:
chmod +x run-audit.sh aggregate.sh
./run-audit.sh # 并发跑完所有仓库
✓ auth-svc :: security-scanner
✓ payment-svc :: security-scanner
./aggregate.sh # 出总报告
总花费:0.0124 USD
高危项数:3
最终的端到端时序如下:
sequenceDiagram
participant U as 运维/CI
participant S as run-audit.sh
participant C as claude -p
participant A as 子代理 security-scanner
participant H as Hooks
U->>S: 启动批量
loop 每个仓库 (并发 -P4)
S->>C: claude --bare -p --agent security-scanner
C->>H: UserPromptSubmit(注入分支/HEAD)
H-->>C: additionalContext
C->>A: 委派审计任务
A->>A: Read/Grep/Glob 扫描
A-->>C: JSON 结论
C->>H: PostToolUse(记委派日志)
C-->>S: .result / structured_output
S->>S: 落 audit-out/*.json
end
S->>U: 全部完成
U->>S: aggregate.sh
S-->>U: audit-report.json + 总花费 + 高危数
--agent 跑主代理时配 initialPrompt:在子代理 frontmatter 写 initialPrompt: "审计当前目录",配合 --agent <name> 即可不传提示词直接 claude -p --agent security-scanner,进一步脚本化。CLAUDE_CODE_SUBAGENT_MODEL 环境变量 > 单次调用 model 参数 > 子代理 frontmatter model > 主会话模型。批量场景可用环境变量临时把所有子代理降到 Haiku 做快速预扫。isolation: worktree:给可能改文件的子代理(如"自动修复"角色)设此字段,让它在临时 git worktree 里干活,不污染主分支,无改动时自动清理。CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1,去掉 Explore/Plan 等内置代理,避免误委派。session_id 后用 --resume 续跑长任务——session_id=$(claude -p "..." --output-format json | jq -r .session_id),再 claude -p "继续" --resume "$session_id"。注意 session ID 按项目目录作用域,须在同一目录下 resume。https://code.claude.com/docs/en/sub-agentshttps://code.claude.com/docs/en/headlesshttps://code.claude.com/docs/en/hooks-guidehttps://code.claude.com/docs/en/sub-agents(同域"Agents and parallel work"章节)https://code.claude.com/docs/en/headless 末尾"Next steps"链接-p/--agent/--output-format 实操约 2 课时,批量脚本与 jq 聚合约 2 课时,Hooks 与 stream-json 实时看板约 1.5 课时,综合实战与复盘约 1 课时。jq、JSON Schema 概念,以及至少跑通过一次 claude -p。可作为"无头模式入门"之后的进阶主项目。--agents/--agent 启动方式与委派机制的官方权威说明。-p 无头模式、--bare、--output-format json/stream-json、--json-schema、--allowedTools、API 重试事件等自动化核心能力。UserPromptSubmit/PostToolUse/Stop 事件、agent_type 字段与 hook 输出契约。.claude/agents frontmatter schema 与自动委派、工具受限的实操补充。[!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 等场景全覆盖。
