# 无头模式与 CI：让 Claude 在流水线里干活

> 不开交互界面，用 claude -p 把 Claude Code 当成一个命令行工具嵌进脚本和 CI；掌握输出格式、权限与 --bare，接入 GitHub Actions 自动评审 PR，并了解什么时候该换成 Agent SDK。

- 网址：https://coding.chengyu.dev/learn/claude-code/headless-ci/
- 学习路径：Claude Code 精通
- 预计用时：30 分钟；核实日期：2026-10-25
- 官方来源：[Run Claude Code programmatically（Claude Code 官方文档 · 无头模式）](https://code.claude.com/docs/en/headless)，[Claude Code GitHub Actions（Claude Code 官方文档）](https://code.claude.com/docs/en/github-actions)，[CLI reference（Claude Code 官方文档）](https://code.claude.com/docs/en/cli-reference)，[Agent SDK overview（Claude Code 官方文档）](https://code.claude.com/docs/en/agent-sdk/overview)，[Agent SDK quickstart（Claude Code 官方文档）](https://code.claude.com/docs/en/agent-sdk/quickstart)，[Set up Claude Code（Claude Code 官方文档 · 安装）](https://code.claude.com/docs/en/setup)

## 学习目标

- 能用 claude -p 配合 --output-format 和 --json-schema 在脚本里拿到可解析的结果
- 能说清无头模式下权限怎么生效，以及为什么 CI 里推荐 --bare
- 能用 claude-code-action 或纯脚本两种方式，在 GitHub Actions 里自动评审 PR
- 能判断一个自动化需求该用 claude -p 还是 Agent SDK

团队五个人，每天十几个 PR。真正细看的评审只能顾上核心模块，其余的大多是扫一眼就点通过。结果上线后才发现：某个接口漏了 `await`，某个配置里明文写了测试环境的密码。

如果每个 PR 一提交，就有一个 Claude 先过一遍、把明显的问题写成评论，人类评审就能把精力留给真正需要判断的地方。这一课就做这件事：让 Claude Code 脱离交互界面，在脚本和 CI 里自动运行。

## 无头模式：claude -p

给 `claude` 加上 `-p`（全称 `--print`），它就不再打开交互界面，而是执行完一次任务、把结果打印到标准输出后退出，像 `grep`、`jq` 一样可以放进管道和脚本。成功时退出码为 0，失败时非 0，脚本可以据此判断。

它会读标准输入，所以最自然的用法是「把东西喂给它」：

```bash
# 把构建日志喂进去，把解释写进文件
cat build.log | claude -p "用三句话说明这次构建失败的根本原因" > why.txt

# 根据暂存区的改动生成一条中文提交信息
git diff --cached | claude -p "根据这段 diff 写一条符合 Conventional Commits 的提交信息，只输出这一行"
```

**输出格式**用 `--output-format` 控制：

| 格式 | 得到什么 | 适合 |
|---|---|---|
| `text`（默认） | 纯文本 | 直接给人看、写进文件 |
| `json` | 一个 JSON 对象，正文在 `result`，另有 `session_id`、费用估算 `total_cost_usd` 等元数据 | 脚本解析、记录花费 |
| `stream-json` | 每行一个 JSON 事件，实时输出（需加 `--verbose`） | 自己做界面、实时展示进度 |

需要固定结构时，再加 `--json-schema` 传入一个 JSON Schema，结果会出现在 `structured_output` 字段里。比如把一批用户反馈分类：

```bash
cat feedback.txt | claude -p "把每条反馈归类为 bug、需求或咨询" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"kind":{"type":"string","enum":["bug","需求","咨询"]}},"required":["text","kind"]}}},"required":["items"]}' \
  | jq '.structured_output.items'
```

如果 Claude 拒绝了任务或没能产出合法结构，`structured_output` 字段会缺失，脚本使用前要先判断它在不在。

多轮任务可以用 `--continue` 接着最近一次对话，或者先从 JSON 里取出 `session_id`，再用 `--resume` 指定接哪一次。

## 无头模式下的权限

交互模式里，Claude 遇到需要批准的操作会停下来问你。无头模式没人可问，规则是这样的：

- 不设置权限模式时，`-p` 运行在 **Manual 模式**（也就是默认模式，v2.1.302 起明确如此），**需要批准的调用一律被拒绝**。读取工作目录里的文件这类本就不需要批准的操作照常进行。
- 用 `--allowedTools` 预先放行，语法和权限规则一样（见[第 3 课](/learn/claude-code/permissions/)）。例如 `--allowedTools "Read,Bash(git diff *),Bash(git log *)"`。注意 `Bash(git diff *)` 里星号前的空格不能省，否则 `git diff-index` 也会被匹配。
- 也可以用 `--permission-mode` 设一个整体基线：`acceptEdits` 自动接受文件编辑，`dontAsk` 拒绝所有本该询问的调用，适合锁得很死的 CI。
- 给它设上限：`--max-turns` 限制轮数，`--max-budget-usd` 限制估算花费，CI 里两个都建议加。

> **CI 里推荐加 --bare**
>
> 不加 `--bare` 时，`claude -p` 会像交互会话一样自动加载当前目录的 hooks、Skills、插件、`.mcp.json` 里的 MCP 服务器和 CLAUDE.md，而且**不弹信任对话框、不逐个批准 MCP 服务器**。如果 CI 检出的是别人提交的代码，等于让对方决定在你的流水线里跑什么。
> 
> `--bare` 跳过这些自动发现，每台机器上结果一致、启动也更快。代价是它不读订阅登录信息，必须用 `ANTHROPIC_API_KEY` 环境变量认证；需要的配置用 `--settings`、`--mcp-config`、`--append-system-prompt` 等参数显式传入。官方表示 `--bare` 将来会成为 `-p` 的默认行为。

## 接入 GitHub Actions

官方提供了 `anthropics/claude-code-action`，它基于 Agent SDK，在 GitHub 的工作流里运行 Claude Code。最快的接入方式是在本地仓库里启动 `claude`，运行 `/install-github-app`：它会安装 Claude GitHub App、把凭据存成仓库 secret、推一个带工作流文件的分支并帮你打开 PR，合并即生效。前提是装好并登录了 GitHub CLI（`gh auth login`）、你对仓库有管理员权限，且仓库在 github.com 上。

想手动配置，就三步：安装 [Claude GitHub App](https://github.com/apps/claude)；在仓库 secret 里放 `ANTHROPIC_API_KEY`（API 计费），或用 `claude setup-token` 生成的 `CLAUDE_CODE_OAUTH_TOKEN`（用订阅额度）；把工作流文件放进 `.github/workflows/`。

这个 Action 有两种工作方式，取决于你有没有写 `prompt`：

| 模式 | 触发条件 | 结果在哪 |
|---|---|---|
| 交互模式（不写 `prompt`） | 有人在 issue 或 PR 评论里 @claude | 在原 issue 或 PR 下回复评论 |
| 自动化模式（写了 `prompt`） | 工作流事件本身，如 PR 打开、定时任务 | 默认在工作流日志里，提示词要求且有发评论的工具时才会发到 PR |

其他常用参数：`claude_args` 透传任意命令行参数（如 `--max-turns`、`--model`、`--allowedTools`），`settings` 传入 settings JSON，`trigger_phrase` 改触发词。还在用 `@beta` 的旧工作流要迁到 `@v1`：去掉 `mode`，把 `direct_prompt` 改成 `prompt`，把 `max_turns`、`model` 这类选项挪进 `claude_args`。

## 实操：在 CI 里自动评审 PR

**方案 A：官方 Action + 代码评审插件（推荐 GitHub 用户）**

这是官方示例里的做法：安装 `code-review` 插件，在 PR 打开或更新时运行它的评审 Skill，把问题以行内评论的形式发在 PR 上。新建 `.github/workflows/claude-review.yml`：

```yaml
name: Claude 代码评审
on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]
jobs:
  review:
    runs-on: ubuntu-latest
    timeout-minutes: 15          # 防止跑飞
    permissions:
      contents: read
      pull-requests: read
      issues: read
      id-token: write            # Action 用 GitHub App 认证时需要
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
          plugins: "code-review@claude-code-plugins"
          prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
          claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'
```

`--comment` 决定评审发到 PR 上，去掉它就只写进运行日志；`claude_args` 那一行不能删，Action 只有在 `--allowedTools` 点名时才会启动发行内评论的工具。草稿 PR、已关闭的 PR 和已有 Claude 评论的 PR 会被跳过。如果你连工作流都不想维护，官方还有一个托管的 Code Review 功能可以了解。

**方案 B：纯脚本版（想完全掌控提示词，或用在其他 CI）**

核心思路：**让 Claude 只负责产出文字，发评论这一步交给脚本**。Claude 手里没有任何写权限，即使 diff 里藏了恶意指令，它也做不了什么。

```yaml
name: AI 评审（脚本版）
on:
  pull_request:
    types: [opened, synchronize]
permissions:
  contents: read
  pull-requests: write
concurrency:
  group: ai-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true       # 连续推送时只保留最新一次
jobs:
  review:
    # 只评审本仓库分支发起的 PR；fork 来的 PR 拿不到 secrets
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v6
      - name: 安装 Claude Code
        run: |
          curl -fsSL https://claude.ai/install.sh | bash
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: 生成评审并发评论
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GH_TOKEN: ${{ github.token }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          gh pr diff "$PR_NUMBER" > pr.diff
          claude --bare -p "你是资深代码评审。下面是一个 PR 的 diff，请用中文列出：1. 可能的 bug 和安全问题（附文件和行号）；2. 明显的性能问题；3. 缺失的测试。没有问题就回复「未发现明显问题」。不要逐行复述 diff。" \
            --max-turns 5 --max-budget-usd 1 \
            --output-format json < pr.diff > review.json
          jq -r '.result' review.json > review.md
          gh pr comment "$PR_NUMBER" --body-file review.md
```

几个设计上的讲究：

- PR 编号通过 `env` 传进脚本，而**不是**把 `github.event.pull_request.title` 这类别人能控制的文字直接拼进 `run`，避免命令注入。
- `--bare` 让 PR 里如果夹带了 `.claude/settings.json` 或 `.mcp.json`，也不会被加载执行。
- `--max-turns`、`--max-budget-usd`、`timeout-minutes`、`concurrency` 四道闸，防止一个 PR 烧掉一大笔钱。
- 换成 GitLab CI 或公司内部的 Jenkins，只要把 `gh pr diff` 和 `gh pr comment` 换成对应平台的命令，主体不用改。

```text
$ curl -fsSL https://claude.ai/install.sh | bash
# 安装输出省略
$ gh pr diff 128 > pr.diff
$ claude --bare -p "你是资深代码评审……" --max-turns 5 --output-format json < pr.diff > review.json
$ jq -r '.result' review.json
  1. src/api/order.ts:42 调用 fetchStock() 时缺少 await，库存校验会被跳过
  2. config/test.yaml:7 明文写入了数据库密码，建议改为读取环境变量
  3. 新增的 calcDiscount() 没有对应的单元测试
$ gh pr comment 128 --body-file review.md
# 输出为示意，实际内容取决于你的 diff
```

**让 Claude Code 为你的仓库搭好 AI 评审流水线**

```text
请为这个仓库添加一个 GitHub Actions 工作流，在 PR 打开和更新时用 claude -p 自动评审，要求：

1. 文件放在 .github/workflows/ai-review.yml，参考「方案 B 纯脚本版」的结构：用 --bare，Claude 只输出文字，发评论由 gh pr comment 完成；
2. 评审提示词结合这个仓库的技术栈来写（先读 package.json 或其他依赖文件判断），用中文输出，按「必须修 / 建议修 / 可以考虑」三级分类；
3. 只对本仓库分支发起的 PR 运行，加上 timeout-minutes、concurrency、--max-turns 和 --max-budget-usd；
4. 不要把任何 PR 标题、正文之类的外部文字直接拼进 run 脚本；
5. 写完后告诉我需要在仓库 Settings 里添加哪个 secret，以及怎么开一个测试 PR 验证。不要替我提交或推送。
```

## Agent SDK：需要更多控制时

`claude -p` 适合「一问一答」式的自动化。如果你要把 Claude Code 的能力嵌进自己的 Python 或 TypeScript 程序，比如做一个内部的「工单自动修复机器人」，需要在每次工具调用时自己决定放不放行、把过程实时显示在自己的界面上，就该用 **Agent SDK**。它提供和 Claude Code 相同的工具、智能体循环和上下文管理，并支持 Hooks、子智能体、MCP、权限和会话。

安装：Python 用 `pip install claude-agent-sdk`，TypeScript 用 `npm install @anthropic-ai/claude-agent-sdk`。最小的 Python 例子：

```python

from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

async def main():
    async for message in query(
        prompt="检查 utils/date.py 中可能导致崩溃的时区处理问题并修复",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Edit", "Glob"],
            permission_mode="acceptEdits",
        ),
    ):
        if isinstance(message, ResultMessage):
            print("完成：", message.subtype)

asyncio.run(main())
```

`query` 返回一个异步迭代器，Claude 每思考一步、每调用一次工具，你都会收到一条消息。认证用 Claude Console 的 API key；官方明确规定，未经许可，第三方开发者不能在自己的产品里让用户用 claude.ai 账号登录或使用其订阅额度。选择上可以这样记：**脚本和 CI 用 `claude -p`，要做成产品或需要精细控制时用 SDK。**

> 小测：你在 CI 里运行 claude -p 「修复 lint 错误」，没有加任何权限相关参数。日志显示 Claude 想编辑文件，但最后什么都没改。最可能的原因是？

> 小测：一个开源项目想用 Claude 自动评审外部贡献者的 PR。下面哪个做法风险最大？

- [ ] 在本地用 cat 某个日志文件 | claude -p 分析一次报错，再加上 --output-format json 用 jq 取出 result
- [ ] 用 --json-schema 写一个能返回固定结构的小脚本，并处理 structured_output 缺失的情况
- [ ] 为一个测试仓库配置方案 A 或方案 B，在仓库 secret 中添加 ANTHROPIC_API_KEY
- [ ] 开一个故意带 bug（比如漏写 await）的测试 PR，确认评审评论出现在 PR 上
- [ ] 检查工作流是否同时具备 timeout-minutes、concurrency、--max-turns 三道限制
