首页 › tutorials › 用--bare造可复现的Claude批量管道

概述

本教程教你把 Claude Code 当作一个标准的 Unix 管道工具来用:用 --bare 隔离本地配置保证可复现,用 -p 无头模式接收 stdin、吐出 stdout,用 --output-format json 拿到带成本与 session 元数据的结构化结果,再用 --resume 断点续跑、用 xargs -P 并发、用 hooks 做监控告警。适合写过 shell 脚本、想把 AI 能力嵌进 CI/批处理流水线的后端、运维和平台工程师。

前置条件


第一步:先理解为什么 --bare 是批量任务的生命线

很多人写自动化脚本的第一反应是直接 claude -p "...",跑一次成了,写进 CI 第二天却挂在同事的机器上。原因在于:普通的 -p 调用会加载和你交互式会话一模一样的上下文——项目里的 .mcp.json、你 ~/.claude 下装的某个 hook、队友提交的一段 CLAUDE.md、自动 memory,全部都会被读进来。

这对"在每台机器上得到相同结果"是致命的。--bare 就是为此而生:

# 普通无头:会吸收本地一切配置,不可复现
claude -p "总结这个项目"

# 裸模式:跳过 hooks/skills/plugins/MCP/auto-memory/CLAUDE.md 的自动发现
claude --bare -p "总结这个项目"

官方文档明确写道:--bare 是脚本和 SDK 调用的推荐模式,未来会成为 -p 的默认行为。它的核心契约有三条,必须牢记:

  1. 不读任何自动发现的配置。你 ~/.claude 里的 hook、项目的 .mcp.json、队友的 CLAUDE.md 全部不会执行。只有你显式通过 flag 传入的东西才生效。
  2. 保留最小工具集:Bash、文件读、文件写三个工具始终可用,不需要额外声明。
  3. 跳过 OAuth 和 keychain 读取。鉴权只能来自 ANTHROPIC_API_KEY 环境变量,或通过 --settings 传入的 apiKeyHelper。如果你在 CI 里忘了设这个变量,--bare 模式会直接报错退出,而不是弹出一个永远没人去点的登录提示——这正是无人值守场景想要的"快速失败"。

需要把上下文显式喂回去时,用下表对应的 flag:

你想注入的内容 对应 flag
系统提示追加 --append-system-prompt "..." 或 --append-system-prompt-file <path>
整份 settings --settings <file-or-json>
MCP 服务器 --mcp-config <file-or-json>
自定义子代理 --agents <json>
插件 --plugin-dir <path> 或 --plugin-url <url>

预期结果:同一段 claude --bare -p ... 命令,在你的笔记本、同事的台式机、CI 的容器里行为一致;换台机器不会再"莫名其妙多读了点东西"。

常见问题:脚本在本地能调 MCP 工具,上了 CI 却报"工具不存在"——因为 --bare 没加载 .mcp.json。解决:用 --mcp-config ./mcp.json 显式带上。


第二步:把 Claude 接进管道——stdin 进、stdout 出

-p 模式天然读取 stdin,于是 Claude 就成了一个可以和 cat、grep、jq 自由串联的管道节点:

# 把构建报错喂给 Claude,把解释写进文件
cat build-error.txt | claude --bare -p '用一句话指出这个构建错误的根因' > output.txt

这里有一个工程上很优雅的写法,把"读文件"这件事交给管道本身,从而完全不需要给 Claude 授予 Bash 权限:

// package.json
{
  "scripts": {
    "lint:claude": "git diff main | claude --bare -p \"你是一个错别字检查器。对这段 diff 里每个错别字,第一行输出 文件名:行号,第二行输出问题,其余什么都不要输出。\""
  }
}

把 diff 通过管道喂进去,Claude 既不需要 Bash 权限去 git diff,脚本也更安全、更可移植(Windows 下双引号转义也保持一致)。

关键限制(踩坑点):从 v2.1.128 起,通过管道传入的 stdin 上限是 10MB。超过会以非零状态码退出并报清晰错误。处理更大输入时,不要硬塞 stdin,而是把内容写到文件、在 prompt 里引用路径:

# 错误:超大日志可能撑爆 stdin 上限
cat huge.log | claude --bare -p "分析这份日志"

# 正确:引用文件路径,让 Claude 自己按需读取
claude --bare -p "分析 ./huge.log 里最近一小时的 ERROR" --allowedTools "Read,Grep"

第三步:用 --output-format json 拿到结构化结果与成本

文本输出对人友好,但对脚本不友好。换成 JSON,你会同时拿到结果正文、session ID、以及每次调用的成本:

claude --bare -p "用三句话总结这个项目的架构" --output-format json > summary.json

返回结构大致如下(字段以实际版本为准):

{
  "type": "result",
  "subtype": "success",
  "result": "该项目采用分层架构……(正文)",
  "session_id": "abc12340-aaaa-bbbb-cccc",
  "total_cost_usd": 0.0123,
  "usage": { "input_tokens": 2048, "output_tokens": 312, "cache_read_input_tokens": 1024 },
  "duration_ms": 4200,
  "num_turns": 1
}

--output-format 有三种取值:

total_cost_usd 和按模型拆分的成本明细是批处理场景的金矿——脚本可以不查用量看板,直接在本地累计每次调用的花费:

# 批量跑 10 个仓库,累计总成本
total=0
for repo in repos/*; do
  cost=$(claude --bare -p "审查 $repo 里的安全风险" \
           --output-format json | jq '.total_cost_usd')
  total=$(echo "$total + $cost" | bc)
  echo "$repo 花费 \$$cost,累计 \$$total"
done

如果你要下游程序按固定字段解析,再加一个 --json-schema 让输出严格符合 schema,把"模型自由发挥的文本"变成"机器可信赖的数据":

claude --bare -p "列出这个仓库所有公开 API 端点" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"endpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"}},"required":["method","path"]}}},"required":["endpoints"]}'

第四步:会话命名 + --resume 实现断点续跑

批量任务最怕跑到第 9 个挂了,前 8 个的上下文全丢、得从头再来。Claude Code 的 session 机制正好能治这个病。

先给会话起个有意义的名字(-n):

claude -n "techdebt-audit-repoA" -p "扫描这个仓库的技术债" --output-format json

从 -p 创建的会话不会出现在交互式的 session 选择器里,但你可以拿到它的 session_id(JSON 输出里有),然后在下一次调用里用 --resume <session-id> 把它接回来——必须在创建该会话的同一个目录里执行(session 查找范围限定在当前项目目录及其 git worktree)。

# 第一次:跑一个大任务,拿到 session_id
SID=$(claude -n "big-refactor" -p "把所有 class 组件改成函数组件,先做 src/components" \
        --output-format json | jq -r '.session_id')

# 中途断了……接回来继续,带着之前的全部上下文
claude --resume "$SID" -p "继续处理 src/pages 目录" --output-format json

四个入口点的区别,务必记清:

命令 作用
claude --continue 恢复当前目录里最近的那个会话
claude --resume 打开交互式 session 选择器
claude --resume <name> 按名字直接恢复(跨同一仓库的 worktree 解析)
claude --resume <session-id> 按 ID 恢复(-p 创建的会话只能用这条)
claude --from-pr <number> 恢复与某个 PR 关联的会话

工程含义:批处理脚本应当把每个子任务的 session_id 持久化到文件,失败重试时优先 --resume,而不是重新发起一个干净的调用——既省 token(命中之前的 prompt cache),又能让模型"记得"它上次的判断。


第五步:用 xargs -P 把串行循环升级为并发批处理

for 循环是串行的:20 个仓库要排着队跑,白白浪费时间。xargs -P 能并发 N 路 Claude:

# 把 20 个仓库的路径喂给 xargs,最多 4 路并发
printf '%s\n' repos/* | xargs -I{} -P4 bash -c '
  repo="$1"
  claude --bare -p "审查 $repo 的依赖许可证合规性,输出 JSON" \
    --output-format json > "reports/$(basename $repo).json"
' _ {}

要点:

并发 + 续跑组合,就是健壮批处理的两根支柱:并发省时间,续跑防丢失。


第六步:后台任务的"五秒宽限"与十分钟天花板

当 -p 调用里 Claude 启动了一个后台 Bash 任务(比如 npm run dev 或一个 watch 构建),它不会立刻被杀——Claude 返回最终结果后,大约有 5 秒宽限期,让刚好在结果之后完成的任务还能把输出送回来,随后那个 shell 被终止。stdin 关闭后整个 -p 进程才真正退出。

两个版本相关的细节:

# 给后台 agent 等待封顶设为 5 分钟
export CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=300000

实战建议:批处理脚本里不要让 Claude 启动长驻后台进程。需要跑测试,就让它前台跑完、拿到退出码;需要 watch,就拆成独立的 cron/CI job。把 -p 调用保持在"有明确终点"的形态,是无人值守稳定性的前提。


第七步:用 hooks 给批处理装上监控与告警

-p 模式同样会触发 hooks(除非用 --bare 显式跳过)。批处理最该用三个事件:

// .claude/settings.json(批处理脚本专用项目配置)
{
  "hooks": {
    "SessionStart": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "echo \"[$(date)] 任务开始 session=$CLAUDE_SESSION_ID\" >> /var/log/batch.log"
      }]
    }],
    "Stop": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "jq -r '\"任务完成,花费 $\\(.total_cost_usd)\"' >> /var/log/batch.log"
      }]
    }],
    "Notification": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "curl -s -X POST $SLACK_WEBHOOK -d '{\"text\":\"Claude 需要你注意\"}'"
      }]
    }]
  }
}

三个事件的分工:

注意:这三类事件的 cadence 不同。SessionStart/SessionEnd 每个会话一次;UserPromptSubmit/Stop/StopFailure 每轮一次;PreToolUse/PostToolUse 每次工具调用一次。批处理成本核算放 Stop 最准,放 SessionEnd 会漏掉多轮调用。但如果你用了 --bare,这些项目级 hooks 默认不会执行——要么改用 --settings 显式注入,要么把日志逻辑写进 shell 脚本本身。


实战案例:批量仓库技术债体检流水线

把上面七步揉成一个完整可运行的脚本 repo-audit.sh。它遍历 repos/ 下所有仓库,并发跑技术债扫描,每个仓库单独出 JSON 报告,失败的自动续跑,最后聚合成本并汇总高危项。

#!/usr/bin/env bash
# repo-audit.sh —— 批量技术债体检
set -uo pipefail

REPOS_DIR="./repos"
REPORTS_DIR="./reports"
STATE_FILE="./reports/state.tsv"   # 记录 repo -> session_id 的映射,用于续跑
PARALLEL="${PARALLEL:-3}"
MAX_RETRIES=2

mkdir -p "$REPORTS_DIR"
touch "$STATE_FILE"

audit_one() {
  local repo="$1"
  local name
  name=$(basename "$repo")
  local report="$REPORTS_DIR/$name.json"

  # 已有报告则跳过(幂等)
  [[ -f "$report" ]] && { echo "skip $name (已有报告)"; return 0; }

  # 查是否有上次中断的 session_id,有则续跑
  local sid=""
  sid=$(awk -F'\t' -v r="$name" '$1==r{print $2}' "$STATE_FILE")

  local cmd=(-p "扫描这个仓库的技术债:1) 标记所有 TODO/FIXME/HACK 并按严重度分级;
                 2) 找出圈复杂度最高的 5 个函数;3) 输出按季度排期的整改建议。
                 严格按 JSON schema 输出。"
           --output-format json
           --json-schema '{"type":"object","properties":{"todo_count":{"type":"number"},"hotspots":{"type":"array","items":{"type":"string"}},"schedule":{"type":"array","items":{"type":"object","properties":{"quarter":{"type":"string"},"item":{"type":"string"}}}}},"required":["todo_count","hotspots","schedule"]}'
           --allowedTools "Read,Grep,Glob")

  if [[ -n "$sid" ]]; then
    cmd=(--resume "$sid" "${cmd[@]}")
  fi

  # 带超时与重试
  local attempt=0
  while [[ $attempt -le $MAX_RETRIES ]]; do
    attempt=$((attempt+1))
    if timeout 600 claude --bare "${cmd[@]}" > "$report.tmp" 2>"$report.err"; then
      # 成功:记录 session_id 以备未来续跑,落正式报告
      local new_sid
      new_sid=$(jq -r '.session_id // empty' "$report.tmp")
      [[ -n "$new_sid" ]] && sed -i "/^$name\t/d" "$STATE_FILE" && printf "%s\t%s\n" "$name" "$new_sid" >> "$STATE_FILE"
      mv "$report.tmp" "$report"
      local cost
      cost=$(jq -r '.total_cost_usd // 0' "$report")
      echo "ok $name (成本 \$$cost)"
      return 0
    fi
    echo "retry $name (第 $attempt 次)" >&2
    sleep 5
  done
  echo "FAIL $name" >&2
  return 1
}
export -f audit_one
export REPORTS_DIR STATE_FILE MAX_RETRIES

# 并发跑
printf '%s\n' "$REPOS_DIR"/* | xargs -I{} -P"$PARALLEL" bash -c 'audit_one "$@"' _ {}

# 聚合:总成本、高危项 top、失败清单
echo "===== 汇总 ====="
echo "总成本: \$$(jq -s '[.[].total_cost_usd] | add' "$REPORTS_DIR"/*.json 2>/dev/null | bc)"
echo "失败仓库: $(comm -23 <(ls "$REPOS_DIR" | sort) <(ls "$REPORTS_DIR" | sed 's/\.json$//' | sort) | tr '\n' ' ')"
echo "全局高危热点 top10:"
jq -s '[.[].result // .[] | .hotspots[]?] | flatten | .[]' "$REPORTS_DIR"/*.json 2>/dev/null | sort | uniq -c | sort -rn | head

脚本里的设计决策(每一条都对应前面某一步):


工作流程图

flowchart TD
    A[repos/ 下 N 个仓库] --> B{xargs -P 并发分发}
    B -->|worker 1| C1[查 state.tsv 是否有 session_id]
    B -->|worker 2| C2[查 state.tsv 是否有 session_id]
    B -->|worker 3| C3[查 state.tsv 是否有 session_id]
    C1 --> D1{有历史 session?}
    C2 --> D2{有历史 session?}
    C3 --> D3{有历史 session?}
    D1 -->|是| E1[--resume SID 续跑]
    D1 -->|否| F1[全新 --bare -p 调用]
    D2 -->|是| E2[--resume SID 续跑]
    D2 -->|否| F2[全新 --bare -p 调用]
    D3 -->|是| E3[--resume SID 续跑]
    D3 -->|否| F3[全新 --bare -p 调用]
    E1 & F1 & E2 & F2 & E3 & F3 --> G[timeout 600 + --json-schema 约束]
    G --> H{退出码 0?}
    H -->|否, 重试≤2| G
    H -->|是| I[写 reports/name.json 记 session_id 到 state.tsv]
    H -->|超次失败| J[记入失败清单]
    I --> K[jq -s 聚合总成本与高危热点]
    J --> K
    K --> L[输出汇总报告]

    style B fill:#e1f5ff
    style G fill:#fff3e0
    style K fill:#e8f5e9

常见问题速查

现象 原因 解决
CI 里 claude -p 一直挂着不动 没加 --bare,本地某个 hook/MCP 卡住了,或在等 OAuth 登录 加 --bare,鉴权改用 ANTHROPIC_API_KEY
stdin 报错"超过 10MB" 管道喂的数据太大(v2.1.128 起 10MB 上限) 写入文件,在 prompt 里引用路径
--resume <sid> 报"找不到会话" 不在创建该会话的目录里执行 cd 回原目录,或检查 worktree 归属
并发跑出现 429 撞上 API 速率限制 降低 -P 并发度,加重试退避
total_cost_usd 字段缺失 用了 text 输出格式 改 --output-format json
后台 watch 进程让 -p 永不退出 旧版本无超时天花板 升级到 v2.1.182+,或设 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS
项目 hooks 在批处理里没触发 --bare 跳过了自动发现 用 --settings ./batch-settings.json 显式注入

延伸阅读


训练营适用性评估


参考来源

  1. Run Claude Code programmatically - Claude Code Docs — 官方无头模式文档,--bare、--output-format、管道 stdin 10MB 上限、后台任务宽限期与 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 的权威出处
  2. Manage sessions - Claude Code Docs — 官方会话管理文档,--continue/--resume/-n 命名/-p 创建会话的 resume 行为的权威说明
  3. Hooks reference - Claude Code Docs — 官方 hooks 全事件参考,SessionStart/Stop/Notification 三种 cadence 与 JSON input/output schema
  4. Claude Code Headless Mode for Batch Tasks and CI/CD — 批处理与 CI/CD 集成的实战博客,含重试、超时、权限收窄、安全审计等 shell 模板
  5. Claude Code Headless · Adriano Melo — 无头模式六大业务场景(运维、安全、合规、DevOps)的 prompt 示例集
  6. Beyond One-Shot Prompts: 5 Claude Code Workflow Patterns Explained — 五种 agentic 工作流模式的理论分类,本教程管道流水线对应其中的 sequential 与 headless 模式

[!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学通信知识星球海报-扫码加入