# Claude Code 无头模式：在脚本和管道里运行 claude -p

> 给 Claude Code 传上 `-p`，它就不进交互会话：接收一条提示词，跑完智能体循环，打印结果，再用一个脚本能判断的状态码退出。`--output-format` 选择 text、json 或 stream-json，没人能回答权限提示，所以用 `--allowedTools` 提前放行工具，`--bare` 则让每台机器上的运行表现一致。

Mindket 编辑部 · 发布于 2026-11-04 · 更新于 2026-11-05 · https://mindket.com/cn/learn/claude-code-advanced/claude-code-headless

## 要点

- `claude -p "提示词"` 打印答案，成功时退出码为 0，失败时非 0，所以 shell 脚本可以据此分支。
- 脚本里没人能回答权限提示。用 `--allowedTools` 提前放行工具，或者设定 `dontAsk` 这样的模式，否则需要批准的调用会被拒绝。
- `--output-format json` 把答案放进 `result`，同时给出 `session_id` 和 `total_cost_usd`。再加 `--json-schema`，校验过的数据就出现在 `structured_output` 里。
- `--bare` 跳过 hooks、插件、MCP 服务器和 CLAUDE.md，让 CI 在哪里都一样。Anthropic 说它将来会成为 `-p` 的默认行为。

## 开始之前

你需要 Claude Code、一个终端，以及运行 JSON 示例用的 `jq`。如果对权限规则还不熟，先读[设置那一课](https://mindket.com/cn/learn/claude-code-advanced/claude-code-settings)，因为无头运行里没人回答提示，全靠规则来决定。

Anthropic 为此写的页面叫 [Run Claude Code programmatically](https://code.claude.com/docs/en/headless)。页面把 `claude -p` 称作 Agent SDK 的命令行形式，第 4 课就建立在 Agent SDK 之上。下面的参数来自这个页面和[命令行参考](https://code.claude.com/docs/en/cli-reference)，读于 2026 年 11 月 4 日，无头运行页面在本课更新时又核对过一遍。

## 跑一条提示词，把数据管道进去，检查退出码

非交互模式会读取 stdin，所以 Claude Code 用起来和任何命令行过滤器一样。下面是 Anthropic 的示例：构建日志送进去，解释以文件的形式出来。

````bash
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
````

1. 运行 `claude -p "What does the auth module do?"`。Claude Code 打印回答然后退出。`-p` 是 `--print` 的缩写。
2. 用上面那条命令把构建日志管道进去。管道输入 stdin 的上限是 10MB，更大的内容请写进文件，并在提示词里写明路径。
3. 用 `echo $?` 读退出码。成功是 0，运行失败是非 0。无效的参数会在运行开始之前报到 stderr。
4. 故意让运行在内部失败，比如在没有认证的情况下运行。Claude Code 会把失败信息当作结果打印到 stdout，所以脚本应该先检查退出状态，再相信 stdout。
5. 点名工具，让 Claude 修改文件：`claude -p "Find and fix the bug in auth.py" --allowedTools "Read,Edit,Bash"`。

## 没人来批准：工具、模式和提示

在 Claude Code v2.1.302 或更高版本里，没设权限模式的 `-p` 运行从 Manual 模式开始。需要批准的调用会被拒绝，除非有 allow 规则、hook、`canUseTool` 回调或 `--permission-prompt-tool` 批准它。所以要做的事，是提前说清楚什么可以运行。

在 CI 里，Anthropic 的[权限模式页面](https://code.claude.com/docs/en/permission-modes)给了一个配合 `dontAsk` 的精确白名单。凡是会弹提示的调用都被拒绝，只有列出的那条命令和文件读取能运行。

````bash
claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read"
````

| 参数 | 效果 | 适合 |
| --- | --- | --- |
| `--allowedTools "Read,Edit"` | 列出的工具不经提示直接运行。它用权限规则语法，所以 `Bash(git diff *)` 放行任何以 `git diff` 开头的命令 | 命令都已知的任务 |
| `--permission-mode acceptEdits` | 文件写入和 `mkdir`、`touch`、`mv`、`cp` 这类常见文件系统命令直接运行。其他 shell 命令仍然需要 allow 条目 | lint 修复和重构 |
| `--permission-mode dontAsk` | 凡是会弹提示的调用都被拒绝。工作目录里的读取和已放行的工具仍然运行 | 严格锁定的 CI |
| `--permission-mode auto` | 由分类器代替你审查大多数操作 | 没人盯着的宽泛任务 |
| `--permission-prompts none` | 凡是会弹提示的调用都被拒绝，并告诉 Claude 不要重试。需要 v2.1.259 或更高版本 | 带权限宿主的定时任务 |

## 输出格式：text、json 和 stream-json

默认的 `text` 输出适合人在终端里看。脚本要的是 `json`：它返回一个对象，答案在 `result` 里，另有 `session_id`、用量元数据、`total_cost_usd` 和按模型拆分的费用。Anthropic 把这个费用称为客户端估算，可能和账单不一致。

想让数据按你定的形状出来，就加上 `--json-schema` 并给一个 JSON Schema。校验过的数据出现在 `structured_output` 字段里。schema 无效时运行会停下，并报 `Error: --json-schema is not a valid JSON Schema`，而 `format` 关键字只当注释读，从不强制执行。

````bash
claude -p "Extract the main function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'
````

| 格式 | 你得到什么 | 怎么读 |
| --- | --- | --- |
| `text` | 纯文本，默认值 | 重定向到文件 |
| `json` | 一个对象，含 `result`、`session_id`、用量和费用。配合 `--json-schema` 还多一个 `structured_output` 字段 | `jq -r '.result'` |
| `stream-json` | 按行分隔的 JSON 事件，最后是一条 `result` 消息。加 `--verbose` 和 `--include-partial-messages` 才有逐词的增量 | 用 `jq` 逐行处理 |

## 继续一段对话，不用猜是哪一段

`--continue` 加载当前目录里最近的一段对话。单独的 `--continue` 会跳过 `claude -p` 产生的会话，而 `claude -p --continue` 把它们算进来。凡是会运行不止一次的任务，都应该从 JSON 输出里取出 ID，恢复那一个确切的会话。

`--resume` 还接受会话名，或者会话 `.jsonl` 记录文件的绝对路径。Claude Code 能在这台机器的任何项目里找到某个 ID，所以两条命令可以在不同目录里运行。继续对话时，`total_cost_usd` 报告的是整段对话的花费，前面几次运行也算在内。`--fork-session` 从旧对话派生出一个新 ID，`--no-session-persistence` 则什么都不保存。

````bash
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
````

## 用 --bare 让 CI 可重复

普通的 `claude -p` 会加载交互会话加载的东西：hooks、技能、插件、MCP 服务器、自动记忆和 CLAUDE.md，来自工作目录，也来自 `~/.claude`。队友的某个 hook 或者一个游离的 `.mcp.json` 就是这样跑进你的 CI 任务里的。加上 `--bare`，Claude Code 把这些全部跳过，Claude 保留 Bash、文件读取和文件编辑工具。

bare 模式从不读取 OAuth 凭据或系统钥匙串。请设置 `ANTHROPIC_API_KEY`，或者在 `--settings` 的 JSON 里提供 `apiKeyHelper`。Amazon Bedrock、Google Cloud 的 Agent Platform 和 Microsoft Foundry 仍然读取各自的凭据。v2.1.286 之前这些限制只部分成立，所以请核对你的版本。

> 没有 `--bare` 时，`-p` 会话会运行项目 `.claude/settings.json` 里的 hooks，并连接其 `.mcp.json` 里的服务器，即使这个文件夹你从未信任过。
>
> Anthropic, Claude Code 官方文档（此处为中文译文）, [Claude Code 文档：Run Claude Code programmatically](https://code.claude.com/docs/en/headless) (2026-11-04 查阅)

````bash
claude --bare -p "Summarize README.md" --allowedTools "Read"
````

| 要加载 | 用 |
| --- | --- |
| 系统提示词的追加内容 | `--append-system-prompt`、`--append-system-prompt-file` |
| 设置 | `--settings`，后面跟文件路径或内联 JSON |
| MCP 服务器 | `--mcp-config` |
| 自定义智能体 | `--agents` |
| 插件 | `--plugin-dir`、`--plugin-url` |

## 写进脚本，并给它设上限

页面里的这个脚本把一个拉取请求的 diff 管道进 Claude，请它做安全审查。把它存成 `review.sh`，运行 `bash review.sh 123`。shell 会把 `"$1"` 换成 `123`，审查结果以 JSON 返回，文字在 `result` 里。

脚本还需要几道上限。文档里写到的有这些。

脚本也离不开它运行时的版本。九月以来有四个版本改了 `-p` 运行的行为，所以在相信一段旧脚本之前，先在运行器上跑 `claude --version`，和这些版本对一对。

````bash
gh pr diff "$1" | claude -p \
  --append-system-prompt "You are a security engineer. Review for vulnerabilities." \
  --output-format json
````

- `--max-turns` 限制打印模式下的智能体回合数，默认没有上限。
- `--max-budget-usd` 在估算花费达到这个金额时停止运行。子智能体的花费也计入，而且花费可能超过上限，所以要留出余量。
- SIGTERM 会让运行以退出码 143 结束，并且不为正在进行的回合记录结果。想干净地结束回合，先发 SIGINT，或者调用 SDK 的 `interrupt()`。
- 给出答案之后，运行还会为它启动的后台工作保持打开。空闲等待默认 10 分钟后停止，`CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` 可以改这个上限。
- `stream-json` 输出里的 `system/init` 事件列出已加载的插件和 MCP 服务器。`mcp_server_errors` 数组非空，说明有服务器根本没加载，所以 CI 关卡可以据此判失败。

**近期 Claude Code 版本里和无头运行有关的改动**

1. **2026年9月2日** v2.1.259 新增 `--permission-prompts none`。凡是会弹出提示的操作都直接拒绝，其余的仍由你设定的权限模式决定。
2. **2026年9月30日** v2.1.286 让 `--bare` 完全生效。bare 运行只连接命令行上点名的 MCP 服务器，不发系统提醒，也不启动后台任务。
3. **2026年10月30日** v2.1.314 修好一个无声的启动失败。启动就失败的 `claude -p` 运行，以前可能以 0 退出且没有任何输出，现在会打印错误并以 1 退出。
4. **2026年11月4日** v2.1.318 不再乱改 `~/.claude.json`。无人值守的运行，也就是 `claude -p` 和 SDK 应用，在文件内容没变时不再重写它。

来源：[Claude Code release v2.1.259 on GitHub](https://github.com/anthropics/claude-code/releases/tag/v2.1.259) (2026-11-05 查阅)；[Claude Code release v2.1.286 on GitHub](https://github.com/anthropics/claude-code/releases/tag/v2.1.286) (2026-11-05 查阅)；[Claude Code changelog](https://code.claude.com/docs/en/changelog) (2026-11-05 查阅)

## Claude Code 的作者为什么要每个脚本都加 --bare

Boris Cherny 是 Claude Code 的作者，他在 [X 上](https://x.com/bcherny/status/2038454357088457168)的一串技巧里，把 `--bare` 列为第 12 条。

默认情况下，`claude -p` 以及 TypeScript 和 Python SDK 会去找本地的 CLAUDE.md、设置和 MCP 服务器。脚本通常更想自己点名要加载什么。

他说这样启动最多能快十倍，还把旧的默认行为称作 SDK 早期的一个设计疏忽。

他在帖子里说，将来的版本会把默认改成 `--bare`。这一步至今还没到来。无头运行页面仍把它称为脚本的推荐模式和将来的默认值，所以这个参数还得你自己加。

现在就把它加进每一条 CI 调用。默认值改掉的那天，你的任务行为不变。在那之前，队友的 hook 或者随手留下的 `.mcp.json` 也混不进运行里。

> 不过在非交互使用时，大多数情况下你会想通过 `--system-prompt`、`--mcp-config`、`--settings` 等参数明确指定要加载什么。
>
> Boris Cherny, Claude Code 的作者，就职于 Anthropic，引文为译文, [X](https://x.com/bcherny/status/2038454357088457168) (2026-11-05 查阅)

[![Boris Cherny（@bcherny）2026 年 3 月 30 日在 X 上发的第 12 条技巧：用 --bare 让 SDK 启动最多快 10 倍，因为默认情况下 claude -p 和 SDK 会搜索本地的 CLAUDE.md、设置和 MCP，而非交互运行通常想自己指定要加载什么。](https://mindket.com/blog/shots/x-bcherny-bare-flag.webp?v=bf0a01397968)](https://x.com/bcherny/status/2038454357088457168)

Boris Cherny 在 X 上，2026 年 3 月 30 日

> X 官方嵌入卡片的截图，拍摄于 2026 年 11 月 5 日，时间为 UTC。X 用“Show more”截断了这条帖子。点击截图可到 X 上打开原帖。

## 常见错误

- 用了 `--bare` 却没有 API 密钥。bare 模式跳过订阅登录，所以在设置 `ANTHROPIC_API_KEY` 之前运行会认证失败。
- 没检查 `structured_output` 是否存在就去读它。运行结束时没有有效的结构化输出，比如 Claude 拒绝了请求，就没有这个字段。
- 在不是你写的仓库里运行 `claude -p`。没有 `--bare` 时，它会运行那个项目的 hooks，并在没有信任对话框的情况下连接它 `.mcp.json` 里的服务器。请用 `--bare` 启动，或者传 `--setting-sources user`。
- 在共用的目录里依赖 `--continue`。它选的是那里最近的一段对话，第二个任务一来就变了。请记下会话 ID。
- 用 SIGTERM 停掉任务，却指望拿到结果。正在进行的回合会被留在未完成的状态。

## 动手试试

- 把 `git diff main` 管道进 `claude -p`，用 `--append-system-prompt` 设一份审查员的说明。分别用 `text` 和 `json` 各跑一次，比较脚本能拿它们做什么。
- 写一段 shell，用 `--output-format json` 运行一条提示词，退出码不是 0 就停下，并用 `jq` 打印 `.result` 和 `.total_cost_usd`。
- 用 `--output-format stream-json --verbose` 运行一条提示词，找到 `system/init` 事件。列出它的插件和 MCP 服务器，再加上 `--bare` 重复一遍并比较。
- 在需要更多回合的任务上设 `--max-turns 3`，记下你的脚本收到了什么。

## 下一课：GitHub Actions 里的 Claude Code

在 CI 的 runner 上，没人盯着运行，密钥还放在它的环境里，上面的一切都会更难。下一课 [GitHub Actions 里的 Claude Code](https://mindket.com/cn/learn/claude-code-advanced/claude-code-github-actions) 会搭好官方 action，以及围绕它的密钥、权限和费用限制。

hooks 会在会话的固定节点运行你自己的命令，而 `--bare` 会跳过它们。hooks 一课列出了 hook 能响应的事件。

## 自测一下

1. 一个 CI 任务在 v2.1.302 或更高版本的 Claude Code 里运行 claude -p，没设权限模式，也没放行 Bash。Claude 想运行的 shell 命令会怎样？

   A. 弹出提示后运行，任务日志里能看到
   B. 被拒绝，因为运行从 Manual 模式开始，没人能批准
   C. 会运行，因为打印模式跳过权限检查

   答案：B。 这个版本里没设模式的 -p 运行从 Manual 模式开始，需要批准的调用会被拒绝，除非规则、hook 或回调批准它。

2. 哪种输出格式返回一个对象，答案在 result 里，并带有 session_id？

   A. text
   B. stream-json
   C. json

   答案：C。 json 格式返回带有结果、会话 ID 和元数据的结构化 JSON，而 stream-json 发送的是按行分隔的事件。

3. 一个任务运行 claude --bare -p "Summarize README.md"，认证失败了。最可能的原因是什么？

   A. bare 模式从不读取 OAuth 凭据或钥匙串，而且没设置 ANTHROPIC_API_KEY
   B. bare 模式需要 --verbose 参数
   C. bare 模式只能配合 --output-format json 使用

   答案：A。 bare 模式跳过订阅登录，所以需要环境里的 API 密钥，或者设置 JSON 里的 apiKeyHelper。

4. 两个任务在同一个目录里运行，第二个必须接着第一个的对话继续。最稳妥的办法是什么？

   A. 两个任务都用 --continue
   B. 传 --no-session-persistence
   C. 单独传 --fork-session
   D. 从 JSON 输出里读出 session_id，传给 --resume

   答案：D。 --continue 选的是目录里最近的对话，而带着记下的会话 ID 的 --resume 指明了确切的那一个。

## 常见问题

### 什么时候该用 Python 或 TypeScript 包，而不是 claude -p？

当脚本需要的东西，是 shell 给不了的。Anthropic 的页面把工具批准回调和原生消息对象，连同结构化输出，列为这些包额外提供的能力。这门课的最后一节就建立在它们之上。

### 怎样为某一次运行更换模型或系统提示词？

用 `--model` 传入 `sonnet`、`opus` 或 `haiku` 这样的别名，或者完整的模型名。`--append-system-prompt` 把文字追加到默认提示词之后，多数脚本用它就够了，而 `--system-prompt` 会替换整个默认提示词。

### 怎样让 claude -p 输出 JSON？

加上 `--output-format json`。答案在 `result` 字段里，旁边是 `session_id` 和费用数据。想要固定的结构，就加 `--json-schema` 再读 `structured_output`，运行结束时没有有效的结构化输出，这个字段就不存在。

### 在 -p 运行里能用技能和斜杠命令吗？

用户调用的技能和自定义命令可以用：在提示词里写 `/skill-name`，Claude Code 会展开它。只能在终端界面里运行的内置命令，比如 `/login`，则不可用。`/model` 等少数几个命令把值当作参数。

### 怎样追踪一次无头运行花了多少钱？

读 JSON 输出里的 `total_cost_usd`，里面还有按模型拆分的明细。Anthropic 把这两个数字都称为客户端估算，可能和账单不同。加上 `--max-budget-usd`，可以在达到上限时停止运行。

### 能在另一个目录里恢复无头会话吗？

能。`--resume` 带上会话 ID，会先搜当前项目和它的 git 工作树，再搜这台机器上的所有其他项目。你也可以传入会话 `.jsonl` 记录文件的绝对路径。

## 资料来源

- [Claude Code docs: Run Claude Code programmatically](https://code.claude.com/docs/en/headless) (2026-11-05 查阅)
- [Claude Code docs: CLI reference](https://code.claude.com/docs/en/cli-reference) (2026-11-04 查阅)
- [Claude Code docs: Permission modes](https://code.claude.com/docs/en/permission-modes) (2026-11-04 查阅)
- [Claude Code release v2.1.259 on GitHub](https://github.com/anthropics/claude-code/releases/tag/v2.1.259) (2026-11-05 查阅)
- [Claude Code release v2.1.286 on GitHub](https://github.com/anthropics/claude-code/releases/tag/v2.1.286) (2026-11-05 查阅)
- [Claude Code changelog](https://code.claude.com/docs/en/changelog) (2026-11-05 查阅)
- [Boris Cherny on X: use --bare to speed up SDK startup](https://x.com/bcherny/status/2038454357088457168) (2026-11-05 查阅)

[浏览技能](https://mindket.com/cn/skills)
