<!-- title: 用--bare造可复现的Claude批量管道 -->

## 概述

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

## 前置条件

- 已安装 Claude Code（`npm install -g @anthropic-ai/claude-code`，建议 v2.1.182 以上，本教程引用了若干该版本前后的行为细节）
- 一台装了 `jq`、`git`、`bash` 的 Linux/macOS 机器
- 已配置 `ANTHROPIC_API_KEY` 或完成 OAuth 登录（注意：`--bare` 模式下不读 OAuth/keychain，必须用 `ANTHROPIC_API_KEY`，下文详述）
- 能看懂基本的 shell 重定向与管道（`|`、`<`、`$(...)`、`xargs`）
- 一个可被反复折腾的代码仓库或一批日志文件作为练手素材

---

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

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

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

```bash
# 普通无头：会吸收本地一切配置，不可复现
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` 自由串联的管道节点：

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

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

```jsonc
// 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 里引用路径：

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

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

---

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

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

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

返回结构大致如下（字段以实际版本为准）：

```jsonc
{
  "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` 有三种取值：

- `text`（默认）：纯文本，适合人读
- `json`：一次性返回完整 JSON，含 `result`、`session_id`、`total_cost_usd` 等
- `stream-json`：换行分隔的 JSON 流，适合实时处理长任务的中间事件（工具调用、思考过程）

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

```bash
# 批量跑 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，把"模型自由发挥的文本"变成"机器可信赖的数据"：

```bash
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`）：

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

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

```bash
# 第一次：跑一个大任务，拿到 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：

```bash
# 把 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"
' _ {}
```

要点：

- **`-P4`** 控制并发度。并发度不是越高越好——它会撞上你的 API 速率限制（RPM/TPM），还会让 `total_cost_usd` 在同一分钟内飙升。建议从 2~4 起步，观察 429 错误后再加。
- **每个调用独立写自己的结果文件**，避免并发写同一个文件相互覆盖。最后再 `jq -s` 聚合：`jq -s '.' reports/*.json > combined.json`
- **`--bare` 在这里是刚需**：并发场景下你绝不希望某个调用意外读到了另一个调用的临时状态，`--bare` 保证每次调用都是从零开始的纯净环境。

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

---

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

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

两个版本相关的细节：

- **v2.1.182 起**，后台子代理/工作流的等待时间默认封顶 **10 分钟**，防止一个卡死的后台 agent 把 `-p` 进程无限期挂起。可以用环境变量 `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` 调整，设为 `0` 表示无限等待。
- 早于 v2.1.163 的版本，一个永不退出的后台进程会把 `claude -p` 调用**无限期挂住**——如果你的 CI 任务莫名其妙永远不结束，先升级版本，再检查是不是有 daemon 没清理。

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

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

---

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

`-p` 模式同样会触发 hooks（除非用 `--bare` 显式跳过）。批处理最该用三个事件：

```jsonc
// .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 需要你注意\"}'"
      }]
    }]
  }
}
```

三个事件的分工：

- **`SessionStart`**（每会话一次）：记录"任务开始了"，落审计日志。
- **`Stop`**（每轮一次）：Claude 结束响应时触发，input 里有 `total_cost_usd`，正好记账。
- **`Notification`**：当 Claude Code 需要推送通知（比如等待权限确认、空闲提醒）时触发——**批处理里看到这个事件通常意味着任务卡住了等人工**，应当立刻推 Slack/钉钉，否则会一直挂着。

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

---

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

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

```bash
#!/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
```

**脚本里的设计决策（每一条都对应前面某一步）**：

- 用 `--bare` 保证在 CI 容器里和本地行为一致（第一步）
- 用 `--json-schema` 强约束输出，下游 `jq` 才能稳定解析（第三步）
- 把 `session_id` 写进 `state.tsv`，失败重试用 `--resume` 续跑而非重头开始（第四步）
- `xargs -P` 控制并发，每个仓库独立写文件避免竞争（第五步）
- `timeout 600` + 重试上限，防止卡死后台任务拖垮整条流水线（第六步）
- 每份报告自带 `total_cost_usd`，末尾用 `jq -s` 聚合总成本（第三步）

---

## 工作流程图

```mermaid
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` 显式注入 |

---

## 延伸阅读

- **Anthropic 官方《Building Effective Agents》**：五种工作流模式（顺序、编排器、拆分合并、agent 团队、无头）的理论框架，本教程的"管道+并发+续跑"对应其中的顺序与无头模式的工程化落地
- **Agent SDK 文档**：当 shell 脚本的表达力不够（需要结构化输出回调、工具审批回调、原生消息对象）时，从 `claude -p` 平滑升级到 Python/TypeScript SDK
- **`Automate actions with hooks` 指南**：hooks 的快速上手版，配合本教程引用的 `Hooks reference` 全事件清单使用
- **`claude --help`** 与 `/docs/llms.txt`：本地最权威的参数清单，版本更新后第一时间核对 flag 是否有变

---

## 训练营适用性评估

- **适合人群**：后端/运维/平台/SRE 工程师，有 shell 脚本基础，想把 AI 嵌进 CI/CD 与定时批处理
- **训练营类型**：DevOps 自动化训练营、AI 工程化落地训练营、平台工程训练营的中高级模块
- **教学时长**：建议 3~4 课时。第 1 课时讲 `--bare`/`-p`/管道三件套并做透第一个管道 demo；第 2 课时讲 JSON 输出、成本追踪与 `--json-schema`；第 3 课时讲 session 续跑与 `xargs` 并发；第 4 课时学员动手实现完整 `repo-audit.sh` 并接入 hooks 监控
- **前置衔接**：若学员已学过"自定义斜杠命令"与"hooks 基础"，可直接进入；零基础学员建议先补 shell 管道与 `jq` 基础
- **考核点**：能否独立写出"幂等、可续跑、成本可见、并发可控"的批处理脚本，并能解释 `--bare` 相比普通 `-p` 在可复现性上的本质差异

---

## 参考来源

1. [Run Claude Code programmatically - Claude Code Docs](https://code.claude.com/docs/en/headless) — 官方无头模式文档，`--bare`、`--output-format`、管道 stdin 10MB 上限、后台任务宽限期与 `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` 的权威出处
2. [Manage sessions - Claude Code Docs](https://code.claude.com/docs/en/sessions) — 官方会话管理文档，`--continue`/`--resume`/`-n` 命名/`-p` 创建会话的 resume 行为的权威说明
3. [Hooks reference - Claude Code Docs](https://code.claude.com/docs/en/hooks) — 官方 hooks 全事件参考，`SessionStart`/`Stop`/`Notification` 三种 cadence 与 JSON input/output schema
4. [Claude Code Headless Mode for Batch Tasks and CI/CD](https://restato.github.io/blog/claude-code-headless-mode/) — 批处理与 CI/CD 集成的实战博客，含重试、超时、权限收窄、安全审计等 shell 模板
5. [Claude Code Headless · Adriano Melo](https://adrianomelo.com/posts/claude-code-headless.html) — 无头模式六大业务场景（运维、安全、合规、DevOps）的 prompt 示例集
6. [Beyond One-Shot Prompts: 5 Claude Code Workflow Patterns Explained](https://www.mindstudio.ai/blog/claude-code-agentic-workflow-patterns) — 五种 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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
