<!-- title: 裸模式配流式监控造批处理流水线 -->

## 概述

本教程聚焦 Claude Code 的 **裸模式（`--bare`）+ 流式输出（`--output-format stream-json`）** 组合，带你从零搭建一条**确定性、可监控、可计费**的 `-p` 批处理流水线。学完你将掌握：为什么 CI 里必须用 `--bare`、如何用 `--json-schema` 拿到可直接入库的结构化结果、如何用流式事件做实时进度与成本看板、以及如何用权限规则锁死自动化边界。适合已会基础 `-p` 调用、想把 Claude Code 真正塞进生产流水线的工程师与 DevOps。

## 前置条件

- 已安装 Claude Code（`claude --version` 能输出版本号，建议 v2.1.182 以上，以获得本文提到的后台等待上限等特性）
- 一个可用的鉴权：`ANTHROPIC_API_KEY` 环境变量（裸模式下不再读取 OAuth/钥匙串）
- `jq`（流式事件解析必备）
- `git`（用于读取被处理文件、限定会话作用域）
- 能跑 `bash` 的环境（Linux/macOS，或 WSL/Git Bash）

---

## 一、先理解痛点：为什么普通 `-p` 不适合进流水线

很多人第一版自动化长这样：

```bash
claude -p "总结 docs/a.md" > out.txt
```

它能跑，但放进 CI 后会出现三类问题：

1. **不确定**：`-p` 默认会像交互式会话一样，加载工作目录与 `~/.claude` 下的全部 hooks、skills、plugins、MCP 服务器、auto memory、CLAUDE.md。同事 A 的机器上跑出结果 X，CI 容器里跑出结果 Y，无法复现。
2. **看不见**：批处理 100 个文件时，stdout 只有最终文本，你不知道跑到第几个、花了多少钱、是不是在重试。
3. **难解析**：纯文本结果要靠正则切，脆弱且不可靠。

这三个痛点，分别对应三个解药：`--bare`、`stream-json`、`--json-schema`。

---

## 二、第一步：用 `--bare` 锁定确定性

`--bare` 是为脚本和 SDK 调用设计的模式，启动时**跳过自动发现** hooks、skills、plugins、MCP、auto memory、CLAUDE.md。它的价值不是"快"，而是"**只有你显式传入的东西才生效**"——这正是 CI 想要的可复现性。

```bash
# 裸模式跑一次性摘要，预批准 Read 工具，避免权限弹窗中断
claude --bare -p "Summarize this file" --allowedTools "Read" < docs/a.md
```

**关键变化：上下文不再靠"它在硬盘上"，而靠你用 flag 显式喂进去。** 裸模式下能加载的内容与对应开关：

| 想加载的东西 | 使用的 flag |
|---|---|
| 系统提示补充 | `--append-system-prompt`、`--append-system-prompt-file` |
| 完整替换系统提示 | `--system-prompt` |
| 配置（含权限、apiKeyHelper） | `--settings <file-or-json>` |
| MCP 服务器 | `--mcp-config <file-or-json>` |
| 自定义子代理 | `--agents <json>` |
| 插件 | `--plugin-dir <path>`、`--plugin-url <url>` |

**鉴权陷阱**：裸模式跳过 OAuth 与钥匙串读取，鉴权**必须**来自 `ANTHROPIC_API_KEY` 或 `--settings` 里 JSON 的 `apiKeyHelper`。Bedrock/Vertex/Foundry 仍用各自厂商凭证。漏掉这一步，CI 里会直接鉴权失败。

```bash
export ANTHROPIC_API_KEY="sk-ant-..."
# 把领域规则作为系统提示注入，保证每台机器行为一致
claude --bare -p "审查这段代码的安全风险" \
  --append-system-prompt "你是安全工程师。只输出 JSON：{risk, items[]}。" \
  --allowedTools "Read" \
  < src/auth.py
```

> **官方说明**：`--bare` 是脚本/SDK 调用的推荐模式，**未来会成为 `-p` 的默认行为**。早点迁移，等于提前为下一次大版本升级免疫。

### 常见问题

- **现象**：本地能跑，CI 报鉴权失败。**原因**：CI 用的是交互登录态，裸模式不读。**解决**：在 CI secrets 里配 `ANTHROPIC_API_KEY`。
- **现象**：`/skill-name` 不生效。**原因**：裸模式不自动发现 skills。**解决**：要么用 `--plugin-dir`/`--agents` 显式带，要么把 skill 指令直接写进 `--append-system-prompt`。

---

## 三、第二步：用 `--json-schema` 拿结构化结果

纯文本结果靠正则切迟早翻车。`--output-format json` 配合 `--json-schema`，能让模型按你给的 JSON Schema 返回，结果落在 `structured_output` 字段，可直接入库或喂给下游。

```bash
claude -p "提取 auth.py 里的函数名" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output.functions'
```

`--output-format json` 的返回体还自带**计费元数据**，无需去用量后台查：

- `result`：纯文本结果
- `session_id`：会话 ID（用于 `--resume` 续跑）
- `total_cost_usd`：本次调用总花费（美元）
- 逐模型成本明细（多模型混合时尤其有用）
- `structured_output`：按 `--json-schema` 校验后的结构化结果

**用 jq 取字段的标准姿势**：

```bash
# 取文本结果
claude -p "总结这个项目" --output-format json | jq -r '.result'
# 取结构化结果
claude -p "提取函数名" --output-format json --json-schema '...' | jq '.structured_output'
```

### 常见问题

- **现象**：`structured_output` 为空或报 schema 错。**原因**：模型输出未严格匹配 schema。**解决**：schema 尽量宽松（少用 `additionalProperties:false`），并在 `--append-system-prompt` 里明确"只输出符合 schema 的 JSON"。
- **stdin 超 10MB 报错**（v2.1.128 起，管道 stdin 上限 10MB）。**解决**：把大文件写到磁盘，在 prompt 里引用文件路径，而不是 `cat | claude`。

---

## 四、第三步：用 `stream-json` 造实时监控

`--output-format stream-json` 会把模型工作过程按事件逐行（newline-delimited JSON）吐出来。配 `--verbose` 和 `--include-partial-messages`，连逐 token 的文本增量都能拿到。

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

每行是一个 JSON 事件。最常用的两类：

**1. 文本增量**（实时打印正在生成的文字）：

```bash
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'
```

`-rj` 的含义：`-r` 输出原始字符串（去掉引号），`-j` 不补换行，于是 token 连续流式打印。

**2. API 重试事件**——这是批处理监控的隐藏宝石。当请求遇到可重试错误，Claude Code 会先发一个 `system/api_retry` 事件再重试。字段如下：

| 字段 | 类型 | 说明 |
|---|---|---|
| `type` | `"system"` | 消息类型 |
| `subtype` | `"api_retry"` | 标识为重试事件 |
| `attempt` | int | 当前重试序号，从 1 起 |
| `max_retries` | int | 允许的总重试次数 |
| `retry_delay_ms` | int | 距下次重试的毫秒数 |
| `error_status` | int/null | HTTP 状态码，连接级错误为 null |
| `error` | string | 错误类别 |

`error` 类别枚举包括：`authentication_failed`、`oauth_org_not_allowed`、`billing_error`、`rate_limit`、`overloaded`、`invalid_request`、`model_not_found`、`server_error`、`max_output_tokens`、`unknown`。

监控重试的意义：批处理跑到一半遇到 `rate_limit`，你能立刻看到"正在第 2 次重试，等 1200ms"，而不是干等到超时。

---

## 五、第四步：用权限规则锁死自动化边界

无人值守必须明确"能干什么、不能干什么"。两条路径：

**路径 A：白名单具体工具**

```bash
claude -p "跑测试并修复失败" --allowedTools "Bash,Read,Edit"
```

`--allowedTools` 用的是**权限规则语法**，支持前缀匹配：

```bash
claude -p "看暂存改动并生成合适 commit" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
```

> **细节坑**：`Bash(git diff *)` 里星号前的**空格不能省**。写成 `Bash(git diff*)` 会把 `git diff-index` 也匹配进去。

**路径 B：设整局权限模式**

- `acceptEdits`：允许写文件，且自动批准 `mkdir`、`touch`、`mv`、`cp` 等常见文件系统命令；其他 shell 命令与网络请求仍需显式 `--allowedTools` 或 `permissions.allow` 规则，否则**尝试时直接中止**。
- `dontAsk`：除了 `permissions.allow` 规则和只读命令集，其余一律拒绝——锁死 CI 的首选。

```bash
claude -p "应用 lint 修复" --permission-mode acceptEdits
```

---

## 六、第五步：会话续跑与后台任务收尾

**续跑**：`--continue` 续最近一次会话；`--resume <session-id>` 续指定会话。

```bash
session_id=$(claude -p "开始一次审查" --output-format json | jq -r '.session_id')
claude -p "继续那次审查" --resume "$session_id"
```

注意：两条命令必须在**同一目录**运行——会话 ID 的查找作用域限定在当前项目目录及其 git worktree。

**后台任务收尾**（v2.1.163/v2.1.182 关键变更）：若 `-p` 运行期间起了后台 Bash 任务（如 dev server），它会在返回最终结果、stdin 关闭后约 **5 秒**被终止。后台子代理/workflow 不受 5 秒限制（其结果是最终输出的一部分），但从 v2.1.182 起，这个等待**默认上限 10 分钟**，防止卡死的后台代理把进程无限期挂住。

```bash
# 调整后台等待上限（毫秒）；设 0 表示无限等待
export CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=600000
```

---

## 实战案例：批量代码安全扫描流水线

下面这条流水线把前面四块拼起来：对 `src/` 下每个 `.py` 文件跑一次裸模式安全扫描，**流式打印进度与重试**，用 `--json-schema` 拿结构化结果，最后汇总总成本。保存为 `sec-scan.sh`：

```bash
#!/usr/bin/env bash
# 用法: ./sec-scan.sh 'src/**/*.py'  并发数
set -euo pipefail
GLOB="${1:-'src/**/*.py'}"; CONCURRENCY="${2:-4}"
export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:?需要 ANTHROPIC_API_KEY}"

SCHEMA='{"type":"object","properties":{
  "risk":{"type":"string","enum":["low","medium","high"]},
  "issues":{"type":"array","items":{"type":"string"}}
},"required":["risk","issues"]}'

scan_one() {
  local f="$1"
  # 裸模式 + 结构化输出 + 流式事件
  claude --bare -p "审查 $f 的安全风险，只按 schema 输出" \
    --append-system-prompt "你是安全工程师，只输出符合 schema 的 JSON。" \
    --allowedTools "Read" \
    --output-format stream-json --verbose \
    --json-schema "$SCHEMA" \
    > "/tmp/scan-events/$$.jsonl" 2>/dev/null

  # 解析事件：实时打印重试，最终取结构化结果与成本
  jq -r 'select(.subtype=="api_retry") | "  ⚠ 重试 #\(.attempt)/\(.max_retries)  \(.error) 等\(.retry_delay_ms)ms"' \
    "/tmp/scan-events/$$.jsonl" >&2
  local risk cost
  risk=$(jq -r 'select(.type=="result") | .structured_output.risk // "?"' "/tmp/scan-events/$$.jsonl" | tail -1)
  cost=$(jq -r 'select(.type=="result") | .total_cost_usd // 0' "/tmp/scan-events/$$.jsonl" | tail -1)
  printf '%s\t%s\t$%s\n' "$f" "$risk" "$cost"
}
export -f scan_one; export SCHEMA
mkdir -p /tmp/scan-events

echo "=== 开始扫描，并发=$CONCURRENCY ==="
# 用 xargs -P 做并行扇出，每行一个文件
find src -name '*.py' -print0 \
  | xargs -0 -P "$CONCURRENCY" -I{} bash -c 'scan_one "$@"' _ {} \
  | tee /tmp/scan-results.tsv

echo "=== 汇总 ==="
# 总成本
awk -F'\t' '{s+=$3} END{printf "扫描文件: %d  总成本: $%.4f\n", NR, s}' /tmp/scan-results.tsv
# 风险分布
awk -F'\t' '{c[$2]++} END{for(r in c) printf "  %s: %d 个\n", r, c[r]}' /tmp/scan-results.tsv
```

**预期输出**（节选）：

```
=== 开始扫描，并发=4 ===
  ⚠ 重试 #1/10  rate_limit 等1500ms
src/auth.py     high    $0.0123
src/utils.py    low     $0.0045
src/db.py       medium  $0.0089
=== 汇总 ===
扫描文件: 3  总成本: $0.0257
  high: 1 个
  low: 1 个
  medium: 1 个
```

**为什么这样设计**：

- `--bare` 让结果与运行机器无关，CI 上和同事本地结果一致；
- `--allowedTools "Read"` 配合 `dontAsk`/`acceptEdits` 思路，把扫描器限制成只读，绝不改你的代码；
- `--json-schema` 让 `risk`/`issues` 可直接 `tee` 成 TSV，再喂给告警系统；
- `stream-json` + jq 把"看不见的黑盒"变成"实时重试提示 + 逐文件成本"，批处理不再是闷头等待；
- `xargs -P` 提供廉价并行，吞吐随并发线性增长（注意 `rate_limit`，过高并发会触发重试，反而变慢）。

### 常见问题

- **总成本对不上**：流式最后一行的 `type=="result"` 才是完整结果，务必 `tail -1`，避免取到中间增量。
- **并发过高全是重试**：`api_retry` 的 `error` 若频繁是 `rate_limit`/`overloaded`，说明撞限流。降低 `-P`，或加自己的退避（看到 `rate_limit` 就 sleep）。
- **CI 里被 `--bare` 卡住**：检查 `ANTHROPIC_API_KEY` 是否注入；裸模式不读交互登录态。

---

## 工作流程图

```mermaid
flowchart LR
    subgraph 输入
        F[文件列表 src/*.py]
    end
    subgraph Worker["裸模式 Worker --bare"]
        P[prompt]
        SP["--append-system-prompt\n领域指令"]
        AT["--allowedTools Read\n只读边界"]
        JS["--json-schema\n结构化契约"]
    end
    subgraph 流式监控["--output-format stream-json"]
        E1[text_delta 逐 token]
        E2["system/api_retry\n重试/限流"]
        E3["result\nstructured_output\ntotal_cost_usd"]
    end
    subgraph 输出
        TSV[scan-results.tsv]
        SUM[成本/风险汇总]
    end
    F --> Worker
    P --> Worker
    SP --> Worker
    AT --> Worker
    JS --> Worker
    Worker -->|逐行事件| 流式监控
    E1 -->|jq 实时打印| 流式监控
    E2 -->|jq 重试告警| 流式监控
    E3 -->|jq 取字段| TSV
    TSV --> SUM
    SUM -.->|不达预期可 --resume 续跑| Worker
```

整条链路的可复现性由 `--bare` 保证（左侧只允许显式输入），可观测性由 `stream-json` 保证（中间三类事件），可解析性由 `--json-schema` 保证（右侧结构化落盘）。三者各管一头，缺一不可。

---

## 进阶组合：把裸模式流水线产品化

当你从"一个脚本"走向"团队工具"时，几个叠加项值得投入：

1. **用 `--settings` 固化团队基线**：把权限规则、`apiKeyHelper`、默认模型写进一份 JSON，所有人/CI 用同一个 `--settings team.json`，比各自 `~/.claude` 更可控。
2. **`--mcp-config` 带上团队 MCP**：扫描器需要查漏洞库？把 MCP 服务器定义进 `--mcp-config vulns.json`，裸模式下显式加载，不依赖本地 `.mcp.json`。
3. **`--agents` 注入专业子代理**：复杂审查可拆成"OWASP 子代理"+"依赖审计子代理"，用 `--agents` 声明，让主代理自动委派。
4. **失败续跑**：批量任务把每个文件的 `session_id` 落盘，失败项用 `--resume` 精准重跑，不必整批重来。
5. **预算闸门**：累加 `total_cost_usd`，超过阈值就停止派发新任务，避免一次跑飞烧光额度。

---

## 训练营适用性评估

| 维度 | 评估 |
|---|---|
| 适合的训练营类型 | DevOps/SRE 进阶营、AI 工程化实战营、企业 AI 落地营 |
| 学员基础 | 已掌握 Claude Code 基础 `-p` 调用与 shell 脚本 |
| 教学时长 | 建议 **3 课时**：1 课时讲 `--bare`/schema 理论，1 课时流式监控实操，1 课时产品化与排障 |
| 动手比重 | 高，全部步骤可直接在学员机器复现 |
| 易踩坑点 | 鉴权（API Key）、`Bash(git diff *)` 空格、流式结果取 `tail -1`、并发限流 |

**核心要点**：本教程的差异化在于把"裸模式确定性 + 结构化契约 + 流式可观测"作为一个整体工程来讲，而非单点介绍某个 flag。学员落地后能得到的是一条**能在 CI 复现、能给老板报成本、能实时看到卡在哪**的流水线，这正是把 Claude Code 从"玩具"推向"生产工具"的临门一脚。

---

## 延伸阅读

- Claude Code 官方 CLI 参考（全部 flag 速查）：`https://code.claude.com/docs/en/cli-reference`
- Agent SDK（Python/TypeScript 包，比 `-p` 更强的程序化控制）：官方文档 Agent SDK 章节
- GitHub Actions / GitLab CI/CD 集成 Agent SDK：官方文档 CI 章节

## 参考来源

1. [Run Claude Code programmatically — Claude Code Docs](https://code.claude.com/docs/en/headless) — 官方裸模式、流式输出、结构化输出、续跑与后台任务收尾的权威说明
2. [CLI reference — Claude Code Docs](https://code.claude.com/docs/en/cli-reference) — 全部 CLI flag 与权限规则语法的完整参考
3. [Claude Code Headless Mode: The Definitive Guide to claude -p — BuildThisNow](https://www.buildthisnow.com/blog/guide/development/claude-code-headless-mode) — 第三方实践博客，覆盖 `-p` 在自动化场景的常见用法
4. [Claude Code in CI/CD and Headless Automation — Hidekazu Konishi](https://hidekazu-konishi.com/entry/claude_code_cicd_and_headless_automation.html) — 实现级指南，讲解 print/headless 模式在 CI/CD 中的无人值守落地
5. [Headless Mode and CI/CD — Cheatsheet | SFEIR Institute](https://institute.sfeir.com/en/claude-code/claude-code-headless-mode-and-ci-cd/cheatsheet/) — 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)
