首页 › tutorials › 批量任务编排:-p配Hooks护栏

批量任务编排:用 Claude Code 的 -p 模式与 Hooks 搭建自动化护栏

概述

本教程面向已经能在交互式终端里用 Claude Code 写代码的工程师,目标是把"人盯着一轮轮对话"的工作方式升级为"脚本批量投递、护栏自动兜底、结果结构化回收"的流水线。我们将围绕 -p 非交互模式展开,从单一调用讲到并发编排,再用 Hooks 在 PreToolUse/PostToolUse/Stop 三个关键节点上挂载确定性脚本,最终交付一个可运行的批量代码评审示例。适用人群:DevOps 工程师、平台/SRE、需要在 CI 与批处理里驱动 Claude Code 的团队技术负责人。

前置条件


正文

第一步:理解 -p 模式与三种输出格式

-p(等价 --print)是 Claude Code 的非交互开关。加上它之后,CLI 不再启动 TUI,而是接收一个提示词、执行、把结果写到 stdout 然后退出——这让它可以像 grep、jq 一样被管道串联。

# 最简单的单次调用
claude -p "auth 模块在做什么?"

# 管道喂入数据,结果重定向到文件
cat build-error.txt | claude -p '简明扼要地说明这次构建错误的根因' > output.txt

--output-format 决定了 stdout 长什么样,这是批处理里最关键的一个旋钮:

格式 用途 典型场景
text(默认) 纯文本 人读、写进 changelog
json 结构化 JSON,含 result、session_id、total_cost_usd 与每模型成本 程序解析、成本归集
stream-json 换行分隔的 NDJSON,逐事件流式 实时进度展示、长任务监控

注意一个容量限制:自 v2.1.128 起,管道喂入的 stdin 上限是 10MB。超出会直接报错并以非零状态码退出。批处理里若要处理超大输入,把内容写进文件、在提示词里引用路径,而不是用管道。

# 结构化输出:把项目摘要取成 JSON
claude -p "总结这个项目" --output-format json | jq -r '.result'

常见问题:调用卡住不返回。多半是触发了权限询问——-p 模式下没有人去点"允许"。解决办法见第三步的 --allowedTools,或在 CI 里用 --permission-mode。另一个坑:忘记 --bare 导致机器上 ~/.claude 里的 hooks/skills/MCP 全被加载,结果在 CI 与本地表现不一致。

第二步:用 --bare 锁定可重复的脚本化运行

这是 v2.1.x 引入的、对批处理最重要的一个标志。--bare 跳过对 hooks、skills、plugins、MCP 服务器、自动记忆和 CLAUDE.md 的自动发现,只认你显式传进去的参数。

# 一次性摘要任务,预批准 Read,纯净环境
claude --bare -p "总结这个文件" --allowedTools "Read"

为什么批处理必须用 --bare?想象你在 CI 跑评审,队友机器上的 ~/.claude 挂了一个 PostToolUse hook 会自动 git commit。不用 --bare,这个 hook 在 CI 里也会触发,把评审产生的临时文件提交进仓库。用了 --bare,本地配置完全不参与,同一份脚本在任何机器上都产生可复现的行为。官方明确指出 --bare 是脚本与 SDK 调用的推荐模式,未来会成为 -p 的默认行为。

--bare 模式下,Claude 仍可用 Bash、文件读、文件编辑这三类基础工具。需要更多上下文时,按需用标志注入:

要加载的内容 对应标志
系统提示词补充 --append-system-prompt / --append-system-prompt-file
设置 --settings <file-or-json>
MCP 服务器 --mcp-config <file-or-json>
自定义 Agent --agents <json>
插件 --plugin-dir <path> / --plugin-url <url>

认证差异:--bare 跳过 OAuth 与钥匙串读取。鉴权必须来自 ANTHROPIC_API_KEY 环境变量,或 --settings JSON 里的 apiKeyHelper。Bedrock/Vertex/Foundry 用各厂商凭证。在 CI 里务必提前把 key 注入环境,否则会以认证失败退出。

第三步:用 --allowedTools 与权限模式给批处理松绑

批处理的头号敌人是"中途弹权限询问"。两种主流解法:

方案 A:白名单工具。 显式列出允许无询问使用的工具:

claude -p "运行测试套件并修复失败项" \
  --allowedTools "Bash,Read,Edit"

--allowedTools 支持权限规则语法,可以细到子命令粒度。下面这个例子只允许四条 git 子命令:

claude -p "查看我暂存的改动并生成合适的提交信息" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"

注意 * 前的空格:Bash(git diff *) 允许任何以 git diff 开头的命令;写成 Bash(git diff*) 会连 git diff-index 一起放行。

方案 B:整会话权限模式。 不逐条列工具,而是设一个基线:

# CI 里锁死模式:只跑允许的,其余直接终止
claude -p "应用 lint 自动修复" --permission-mode dontAsk

常见问题:bypassPermissions 与 --allowedTools 同时使用会产生意外行为——bypassPermissions 会盖过白名单,等于批准一切。CI 里要么用 dontAsk + 白名单(安全),要么完全隔离后用 bypassPermissions(激进),不要混搭。

第四步:结构化输出与 JSON Schema 约束

批处理要把 N 个任务的结果聚合成报表,靠纯文本解析很脆。用 --output-format json 配 --json-schema 可以强制模型返回符合 schema 的对象,放进 structured_output 字段:

# 抽取 auth.py 里的函数名,强制返回字符串数组
claude -p "从 auth.py 中抽取主函数名" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'

返回包里除了 structured_output,还带 session_id、total_cost_usd、每模型的成本明细。批处理脚本可以把这些字段汇总,做成本归集与会话追踪:

# 取出文本结果与会话 ID,便于后续续跑
RES=$(claude -p "总结项目" --output-format json)
echo "$RES" | jq -r '.result'
echo "$RES" | jq -r '.session_id'

常见问题:schema 写得太复杂模型不遵守。建议 schema 尽量扁平、字段少、用枚举约束;对长文本字段不要用 maxLength 苛求,模型对数值约束不敏感。

第五步:会话续跑——批量任务的"断点恢复"

批处理常遇到任务超长、中途失败需要重试的场景。--continue 和 --resume 让一次 -p 调用能接上前一次的上下文:

# 第一次:启动评审
claude -p "评审这个代码库的性能问题"

# 继续最近一次对话
claude -p "现在重点看数据库查询" --continue
claude -p "生成所有发现问题的汇总" --continue

并发跑多个会话时,靠 --resume + session ID 定位具体会话。关键点:session ID 查找的作用域是当前项目目录及其 git worktree,所以续跑命令必须与首次调用在同一目录下执行:

session_id=$(claude -p "启动评审" --output-format json | jq -r '.session_id')
claude -p "继续那次评审" --resume "$session_id"

这构成了无人值守 Agent 的基础:把长任务拆成多轮,每轮产出可检查的中间结果,失败时 --resume 续上,而不是从头再来。

第六步:批量任务的串行与并行编排

把单次调用组装成批量,有两条路。

串行(顺序依赖、稳):用一个 shell 循环,配合 --continue 在同一会话里逐步推进,或每轮独立。串行的好处是成本可预测、不抢配额:

# 对目录里每个 markdown 文件做摘要,结果落盘
for f in docs/*.md; do
  name=$(basename "$f" .md)
  claude --bare -p "为以下文档生成一段 50 字摘要:$(cat "$f")" \
    --output-format json \
    | jq -r '.result' > "summaries/${name}.txt"
done

并行(独立任务、快):用 xargs -P 或 GNU parallel 控制并发度。注意 API 限流——并发太高会撞 rate limit。建议从 -P 4 起步,观察 429 后再调:

# 并发评审多个仓库,最多 4 个同时
find repos -maxdepth 1 -mindepth 1 -type d | \
  xargs -I{} -P 4 sh -c '
    repo=$(basename "$1")
    claude --bare -p "评审 $1 的代码质量,输出三条最关键问题" \
      --allowedTools "Read,Grep,Glob" \
      --output-format json \
      | jq -r ".result" > "reviews/${repo}.json"
  ' _ {}

并发编排的三个要点: 1. 每个任务独立会话:不要在并行任务间共享 --continue,会话状态会串。 2. 成本上限:用 --output-format json 回收 total_cost_usd,脚本里累加,超预算就 break。 3. 失败隔离:单个任务非零退出不应拖垮整批,用 || true 或捕获退出码单独记录。

第七步:用 Hooks 给批处理装上确定性护栏

Hooks 是 Claude Code 在生命周期节点上自动执行的脚本,是批处理"放心无人值守"的关键。批处理最常用三类事件:

事件 触发时机 批处理用途
PreToolUse 工具调用执行前 拦截危险命令(rm -rf、外发请求)
PostToolUse 工具调用成功后 自动 lint、格式化、写审计日志
Stop 一轮回答结束 跑测试、校验产出、触发下游

配置写在 .claude/settings.json(项目级,可提交)或 ~/.claude/settings.json(用户级)。结构是三层嵌套:事件 → matcher 组(过滤) → handler(执行的命令)。

下面是一个拦截 rm -rf 的 PreToolUse hook,matcher 用 "Bash" 精确匹配工具名,if 用权限规则语法进一步缩到 rm 子命令:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

block-rm.sh 从 stdin 读 JSON 输入,判断命令是否含 rm -rf,含则输出 deny 决策:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "破坏性命令被 hook 拦截"
    }
  }'
else
  exit 0   # 无决策,走正常权限流程
fi

执行流程:事件触发 → matcher 检查("Bash" 匹配工具名)→ if 检查(Bash(rm *) 匹配子命令,不匹配就连进程都不起,省开销)→ handler 运行 → Claude Code 读取决策并拦截。

matcher 语法要点: - "*"、空或省略:匹配全部。 - 只有字母数字下划线和 |:精确匹配或 | 分隔的精确列表(Bash、Edit|Write)。 - 含其他字符:当作 JS 正则(^Notebook、mcp__memory__.*)。 - 匹配 MCP 工具用 mcp__<server>__<tool>,匹配整台服务器用 mcp__memory__.*(必须带 .*,否则被当精确字符串,一个都匹配不上)。

PostToolUse 做自动格式化是批处理高频场景,matcher 用 Edit|Write:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

护栏 vs 权限的边界:if 过滤是 best-effort 的(无法解析的命令会 fail-open 直接放行)。要硬性阻止某类操作,用权限系统的 permissions.deny,不要指望 hook。Hook 适合"顺便做点事"(格式化、日志、软警告),权限适合"绝不允许"。

第八步:stream-json 监控长任务的实时状态

批量跑长任务时,stream-json 让你边跑边看进度、边捕获重试事件:

claude -p "解释递归" --output-format stream-json --verbose --include-partial-messages

每行是一个 JSON 事件。用 jq 过滤出文本增量,实现"打字机"效果:

claude -p "写一首关于编译原理的诗" \
  --output-format stream-json --verbose --include-partial-messages \
  | jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'

对批处理更有价值的是两类系统事件:

  1. system/api_retry:API 请求失败重试前发出,含 attempt、max_retries、retry_delay_ms、error_status、error(如 rate_limit/overloaded/billing_error)。批处理可以据此实现自定义退避或熔断——连撞 N 次 rate_limit 就暂停整个批次。

  2. system/init:会话首个事件,报告已加载的 model、tools、MCP servers、plugins,以及 plugin_errors。CI 里可以检查 plugin_errors 是否非空,有插件没加载成功就让流水线失败,避免"静默降级"。

第九步:后台任务与会话收尾的坑

-p 模式下,如果 Claude 起了一个后台 Bash 任务(比如 dev server 或 watch build),该任务会在 Claude 返回最终结果、stdin 关闭后约 5 秒被终止。这个宽限期是为了让"刚好在结果之后完成"的任务还能交付输出。重要变化:v2.1.163 之前,一个永不退出的后台进程会把整个 claude -p 调用无限期挂住——升级到新版后这个问题消失。如果你的 CI 里曾经莫名其妙卡死,升级是解法。


实战案例:批量代码评审流水线

下面这个完整示例把前面所有概念串起来:扫描多个仓库,每个仓库并行跑一次安全评审,用 --json-schema 强制结构化输出,用 hook 兜底拦截危险命令,最后聚合成报表。

目录结构:

batch-review/
├── repos/                  # 待评审的若干仓库(子目录)
├── reviews/                # 评审结果输出
├── run-review.sh           # 主编排脚本
└── .claude/
    ├── settings.json       # hook 配置
    └── hooks/
        └── block-dangerous.sh

1. 护栏 hook(.claude/hooks/block-dangerous.sh,记得 chmod +x):

#!/bin/bash
# 拦截 rm -rf、curl/wcurl 外发、以及 git push 到 main
COMMAND=$(jq -r '.tool_input.command // ""')
if echo "$COMMAND" | grep -qE 'rm -rf|curl |wget |git push.*main'; then
  jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"批处理护栏:禁止的危险操作"}}'
else
  exit 0
fi

2. hook 配置(.claude/settings.json):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)|Bash(curl *)|Bash(wget *)|Bash(git push *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh"
          }
        ]
      }
    ]
  }
}

3. 主编排脚本(run-review.sh):

#!/bin/bash
set -u
mkdir -p reviews
SCHEMA='{"type":"object","properties":{"issues":{"type":"array","items":{"type":"object","properties":{"severity":{"type":"string","enum":["high","medium","low"]},"file":{"type":"string"},"problem":{"type":"string"}},"required":["severity","file","problem"]}}},"required":["issues"]}'

# 并发评审,最多 3 个仓库同时
find repos -maxdepth 1 -mindepth 1 -type d | \
  xargs -I{} -P 3 sh -c '
    repo=$(basename "$1")
    echo "[$(date +%H:%M:%S)] 评审 $repo ..."
    claude --bare -p "对 $1 做安全评审,找出最关键的 3 个问题,只读不要修改任何文件" \
      --allowedTools "Read,Grep,Glob" \
      --output-format json \
      --json-schema '"'$SCHEMA'"' \
      > "reviews/${repo}.json" 2> "reviews/${repo}.err"
    code=$?
    if [ $code -ne 0 ]; then
      echo "[$(date +%H:%M:%S)] ✗ $repo 失败 (exit $code),见 reviews/${repo}.err"
    else
      cnt=$(jq ".structured_output.issues | length" "reviews/${repo}.json" 2>/dev/null || echo 0)
      echo "[$(date +%H:%M:%S)] ✓ $repo 完成,发现 $cnt 个问题"
    fi
  ' _ {}

# 汇总:合并所有仓库的高危问题,算总成本
echo "===== 高危问题汇总 ====="
jq -s '[.[] | .structured_output.issues[]? | select(.severity=="high")] | "共 \(length) 个高危问题"' reviews/*.json
echo "===== 本次总成本 ====="
jq -s '[.[].total_cost_usd] | add' reviews/*.json

4. 运行:

chmod +x run-review.sh .claude/hooks/block-dangerous.sh
./run-review.sh

预期输出:

[14:02:11] 评审 repo-a ...
[14:02:11] 评审 repo-b ...
[14:02:11] 评审 repo-c ...
[14:03:47] ✓ repo-b 完成,发现 3 个问题
[14:04:02] ✓ repo-a 完成,发现 2 个问题
[14:04:19] ✗ repo-c 失败 (exit 1),见 reviews/repo-c.err
===== 高危问题汇总 =====
"共 2 个高危问题"
===== 本次总成本 =====
0.1427

这个流水线体现了批量编排的全部要素:--bare 保证可复现、--allowedTools 锁死只读、--json-schema 强制结构化、hook 兜底防越界、xargs -P 控并发、jq -s 做跨文件聚合、成本字段做预算归集。把 repos/ 换成 PR 列表、issue 列表、日志文件列表,同一套骨架就能复用。


工作流程图

flowchart TD
    A[批量任务输入<br/>repos / PRs / files] --> B[编排脚本 run-review.sh]
    B --> C{xargs -P 并发分发}
    C -->|任务1| D1[claude --bare -p]
    C -->|任务2| D2[claude --bare -p]
    C -->|任务N| DN[claude --bare -p]

    D1 --> E1[生命周期 Hooks]
    D2 --> E2[生命周期 Hooks]
    DN --> EN[生命周期 Hooks]

    subgraph Hooks护栏
        E1 --> F1[PreToolUse<br/>拦截危险命令]
        F1 --> G1[Claude 执行工具调用]
        G1 --> H1[PostToolUse<br/>自动格式化/日志]
        H1 --> I1[Stop<br/>校验产出]
    end

    I1 --> J1[结构化输出 JSON<br/>structured_output + session_id + cost]
    J1 --> K[reviews/*.json 落盘]
    K --> L[jq -s 聚合报表<br/>问题汇总 + 总成本]
    L --> M{失败的任务?}
    M -->|是| N[--resume 续跑]
    N --> C
    M -->|否| O[完成]

    style F1 fill:#fdd,stroke:#c33
    style H1 fill:#dfd,stroke:#393
    style J1 fill:#ddf,stroke:#339

延伸阅读


训练营适用性评估

维度 评估
适合的训练营类型 DevOps/SRE 进阶营、平台工程营、AI 工程化营、企业级 CI/CD 实战营
学员基础要求 熟练 shell 脚本与 jq,用过 Claude Code 交互模式,理解 git 与 CI 基本概念
预计教学时长 6–8 学时(讲授 2h + 动手实验 4h + 流水线联调 2h)
实验环境 每人一台 Linux/macOS VM,预算约 $5–20 API 额度用于跑真实评审
难点 并发限流调参、hook 的 if/matcher 语法细节、--bare 与本地配置的取舍
衔接课程 可作为"Skill 开发"与"MCP 开发"之后的高阶自动化模块,前置建议先修交互模式与权限系统

参考来源

  1. Run Claude Code programmatically — Claude Code Docs — -p 模式、--bare、三种输出格式、--allowedTools、权限模式与会话续跑的官方权威说明。
  2. Hooks reference — Claude Code Docs — 全部 hook 生命周期事件、配置 schema、matcher 语法、command/http/prompt/agent/mcp_tool 五种 handler 类型与 JSON 输入输出格式。
  3. Configure permissions — Claude Code Docs — bypassPermissions 与 allowedTools 的交互、dontAsk/acceptEdits 权限模式语义。
  4. Claude Code Hooks: Complete Guide with 20+ Ready-to-Use Examples (2026) — aiorg.dev — 自动格式化、提交、拦截等可复制配置的实战合集。
  5. Claude Code stream-json: the output format that changes everything — backgroundclaude.com — 三种输出格式对比与 stream-json 事件流的工程化用法。

本文由微信公众号「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 等场景全覆盖。

51学通信知识星球海报-扫码加入