5.2 headless 脚本化:claude -p 进流水线
课型:实战课 | 难度:⭐⭐⭐⭐ | 前置课:第 3 篇(建议先修 1.8 速查表①:高频命令、快捷键与 CLI 参数) | 预估学习时长:40-60 分钟 | 本课交付物:一个接进 package.json/CI 的 AI 审查脚本
到现在为止,你用 Claude Code 的方式都是「你说一句、它做一步、弹窗你点允许」——但 CI 流水线里没有人守着点「允许」,pre-commit 钩子也不会等你聊完天。headless(非交互)模式解决的就是这一个问题:给命令加一个 -p 参数,Claude Code 就从聊天窗口变成一个标准命令行工具——stdin 进、stdout 出,能被管道、脚本和 CI 像调用 grep 一样调用(官方 headless 文档)。做完这课,你会得到一个接进 package.json 的 AI 审查脚本:npm run lint:claude 一敲,Claude 替你检查整个 diff。
本课命令速查
| 命令 / 参数 | 作用 | 何时用 |
|---|---|---|
claude -p "提示词" | 非交互执行,结果打到 stdout(-p 即 --print) | 一切脚本化场景的起点 |
cat 文件 | claude -p "…" | 从 stdin 喂数据 | 日志分析、diff 审查 |
--output-format json | 输出结构化 JSON(含结果、会话 ID、成本) | 脚本要解析结果时 |
--json-schema '<schema>' | 强制输出符合指定 JSON Schema | 要提取字段而非读文章时 |
--allowedTools "Bash,Edit" | 免弹窗放行指定工具 | 需要 Claude 动手改东西时 |
--permission-mode acceptEdits | 放行全部文件写入 | 批量修改类任务 |
--continue / --resume <id> | 接着上一次/指定会话继续 | 多步流水线共享上下文 |
以上参数均出自 官方 CLI reference 与 headless 文档。
[!important] 关于
--bare官方推荐 CI 脚本加--bare,跳过 hooks/MCP/记忆加载,保证每台机器结果一致(headless 文档)。但 bare 模式跳过 OAuth、只认ANTHROPIC_API_KEY——订阅登录的机器上加--bare会直接报Not logged in(本课实测,见排错表)。本课在订阅机上练习,全程不加--bare;等你接入 CI 并配好 API Key(详见 6.1 第一次 API 调用)再加回来。
一、-p:把 Claude 变成一条管道命令
交互会话里,Claude Code 的价值是「陪你干活」;加上 -p 后,它的价值变成「无人值守地干完一件事就退出」。这就是它能进流水线的原因:流水线要的不是对话,而是输入 → 处理 → 输出 → 退出码这个标准回路。原理不展开——-p 模式跑的是同一个 agent loop,详见 1.4 Claude Code 如何工作:agent loop、内置工具与权限确认。
下面动手。整个 Runbook 在一个一次性实验项目里进行,做完即删,不碰你的真实项目。
[!note] 预期输出怎么对 本课所有「预期输出」都是作者 2026-07-03 在 Ubuntu 24.04、Claude Code 2.1.199 上的实测原文。AI 生成的文字每次措辞会有差异,自检标准是格式一致、要点一致,不是逐字相同;带
#注释标出的行才是硬性判据。
步骤 1:确认 Claude Code 版本
引导句:headless 模式的行为随版本演进(如 stdin 上限、后台任务处理都在 2.1.x 中调整过),先确认你的版本不低于本课实测版本,后面输出对不上时才有排查基准。
命令块:
# 查看 Claude Code 版本
claude --version预期输出:
2.1.199 (Claude Code)
# 不低于 2.1 即可跟做本课⚠️ 如果不符:
- 若报
command not found→ Claude Code 未安装或不在 PATH,回到 1.1 安装与首次登录(macOS Windows Linux WSL)。 - 若版本低于 2.1 → 交互模式里跑
/update升级后重试。
步骤 2:搭一个一次性实验项目
引导句:我们需要一个干净的 git 仓库来演示「AI 审查 diff」,并故意埋两处错别字当靶子。全部文件都是一次性的,最后一并删除。
命令块:
# Step 1: 建实验目录并初始化 git
mkdir ~/demo-lab && cd ~/demo-lab
git init -b main
# Step 2: 写入演示模块并提交为基线
cat > greet.js <<'EOF'
// 一个演示用的小模块:问候语生成器
function makeGreeting(name) {
if (!name) {
return 'Hello, stranger!';
}
return `Hello, ${name}!`;
}
module.exports = { makeGreeting };
EOF
git add -A && git commit -m "初始提交"
# Step 3: 制造一个埋了错别字的未提交改动(英文 Helo + 中文"问侯语")
sed -i "s/Hello, stranger/Helo, stranger/" greet.js
sed -i "s|// 一个演示用的小模块:问候语生成器|// 一个演示用的小模块:问侯语生成器|" greet.js
# Step 4: 伪造一份构建失败日志,留给管道实验用
cat > build-error.txt <<'EOF'
$ npm run build
> demo-lab@1.0.0 build
> node build.js
Error: Cannot find module '/tmp/demo-lab/build.js'
at Module._resolveFilename (node:internal/modules/cjs/loader:1212:15)
EOF预期输出:
# git commit 后应看到(哈希值每人不同):
[main (root-commit) 2b08b8f] 初始提交
1 file changed, 9 insertions(+)
# git status 应显示 greet.js 已修改、build-error.txt 未跟踪⚠️ 如果不符:
- 若
git init -b main报不认识-b→ git 版本过旧,先git init再git branch -m main。
回滚:
# 实验做完后整体删除,不留任何痕迹
rm -rf ~/demo-lab步骤 3:第一次非交互调用
引导句:先跑最小可用的一条 -p,确认无人值守回路是通的:命令发出、Claude 回答、进程退出,全程无弹窗。
命令块:
# 在实验目录里发起第一次非交互调用
cd ~/demo-lab
claude -p "用一句话回答:Claude Code 的非交互模式用哪个命令行参数开启?"预期输出:
非交互模式用 `-p`(等同 `--print`)参数开启,例如 `claude -p "你的提示"`。
# 实测耗时 6.8 秒;要点判据:答案提到 -p / --print 且进程自动退出⚠️ 如果不符:
- 若报
Not logged in · Please run /login→ 你多半加了--bare,或本机从未登录过;见文末排错表第一条。 - 若长时间无输出 → 网络问题,先在交互模式确认能正常对话,再回来重试。
步骤 4:像用 grep 一样用管道喂数据
引导句:-p 模式会读 stdin,这意味着任何命令的输出都能直接灌给 Claude——这是它接入现有流水线的关键接口(官方 headless 文档)。用刚才伪造的构建日志试一次。
命令块:
# 把构建失败日志管道喂给 Claude 做根因分析
cat build-error.txt | claude -p "用一句中文说明这份构建日志的根本原因"预期输出:
根本原因:`package.json` 的 build 脚本要执行 `node build.js`,但项目里根本
不存在 `build.js` 这个入口文件,Node 找不到模块便直接报错退出。
# 要点判据:指出 build.js 不存在。实测耗时 7.6 秒⚠️ 如果不符:
- 若 Claude 答非所问 → 确认管道左侧确实有内容(
cat build-error.txt单独跑一遍)。 - 若报输入过大错误 → stdin 有 10MB 上限,见排错表第三条。
二、--output-format json:让脚本读懂 Claude 的回答
管道解决了「怎么喂进去」,JSON 输出解决「怎么取出来」。纯文本适合人读;脚本要判断成败、取会话 ID、算成本,就需要结构化字段(官方 headless 文档)。
步骤 5:拿到结构化 JSON 并用 jq 提取
引导句:给调用加 --output-format json,输出会变成一个 JSON 对象;配合 jq 就能在脚本里精确取用任何字段。这一步顺带认识几个流水线里最常用的字段。
命令块:
# Step 1: 以 JSON 格式提问并落盘
claude -p "用一句话总结这个项目是干什么的" --output-format json > out.json
# Step 2: 提取正文
jq -r '.result' out.json
# Step 3: 提取流水线关心的元数据(成败/成本/耗时/会话)
jq '{total_cost_usd, duration_ms, num_turns, session_id, is_error}' out.json预期输出:
# jq -r '.result' 输出(措辞会有差异,要点:识别出 makeGreeting 模块):
这个项目是一个演示用的极简 JavaScript 模块,导出一个 `makeGreeting` 函数,
根据传入的名字生成问候语(无名字时返回默认招呼语)。
# jq 元数据输出(数值每次不同,字段名是硬性判据):
{
"total_cost_usd": 0.21014850000000002,
"duration_ms": 9696,
"num_turns": 2,
"session_id": "8a493acb-9adc-4012-98fd-0a162182fefb",
"is_error": false
}⚠️ 如果不符:
- 若
jq报command not found→ 先装 jq(Ubuntu:sudo apt install jq)。 - 若
.result为 null → 看is_error与permission_denials字段定位原因。
[!tip] 成本就在字段里
total_cost_usd让脚本无需查用量面板就能逐次记账(官方 headless 文档)。上面这次两轮调用实测 $0.21——把它写进 CI 日志,月底就知道这条流水线花了多少钱。
三、组装:一个真实可用的 AI 错别字 linter
前两个知识点各解决半件事,现在拼成完整交付物。思路来自官方 build-script 示例(官方 headless 文档):把 diff 管道喂给 Claude,让它只输出问题清单。用管道喂 diff 而不是让 Claude 自己跑 git 命令,好处是全程只读、无需任何工具授权。
图:AI linter 的数据流——diff 从 stdin 进,报告从 stdout 出,Claude 全程不需要写权限。
步骤 6:把审查固化成脚本
引导句:一次性命令验证了可行性,固化成脚本 + npm script 才能被团队和 CI 复用。这一步产出本课交付物。
命令块:
# Step 1: 写审查脚本
cat > lint-claude.sh <<'EOF'
#!/usr/bin/env bash
# AI 错别字检查:把当前分支相对 main 的 diff 交给 claude -p 审查
set -euo pipefail
git diff main | claude -p "你是错别字检查器。对这份 diff 里每处错别字或拼写错误,第一行输出 文件名:行号,第二行输出问题说明。除此之外不要输出任何内容。若没有发现问题,只输出 OK。"
EOF
chmod +x lint-claude.sh
# Step 2: 注册进 package.json
cat > package.json <<'EOF'
{
"name": "demo-lab",
"version": "1.0.0",
"scripts": {
"lint:claude": "./lint-claude.sh"
}
}
EOF预期输出:
# 无输出即成功;ls -l lint-claude.sh 应显示可执行位 x⚠️ 如果不符:
- 若后续执行报
Permission denied→ 漏了chmod +x,补上即可。
回滚:
# 本步只在实验目录新增两个文件,删除即回滚
rm -f ~/demo-lab/lint-claude.sh ~/demo-lab/package.json步骤 7:验证步——端到端跑通交付物
引导句:最后一步验证全流程:npm script 调脚本、脚本喂 diff、Claude 抓出步骤 2 埋的两处错别字。这条命令通过,本课交付物即验收合格。
命令块:
# 端到端验证:AI linter 应抓出埋好的两处错别字
npm run lint:claude预期输出:
> demo-lab@1.0.0 lint:claude
> ./lint-claude.sh
greet.js:4
"Helo" 拼写错误,应为 "Hello"
greet.js:6
"问侯语" 用字错误,应为 "问候语"
# 硬性判据:两处错别字(Helo / 问侯语)都被指出,exit code 为 0
# 实测耗时 11.7 秒⚠️ 如果不符:
- 若输出
OK→ Claude 没看到 diff:确认你没有把步骤 2 的改动 commit 掉(git status应显示 greet.js 已修改)。 - 若只抓到一处 → 重跑一次;仍漏则把提示词中"错别字或拼写错误"改得更具体(如明示"含中文用字错误")。
验证通过后,按步骤 2 的回滚命令删掉 ~/demo-lab,实验不留痕迹。
变体玩法
示例 1:--json-schema 强制结构化提取
要的不是一段话而是能直接进程序的数据时,给调用加 JSON Schema 约束,结果出现在 structured_output 字段(官方 headless 文档):
# 从代码里提取函数名,强制返回字符串数组
claude -p "列出 greet.js 导出的函数名" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
| jq '.structured_output'实测输出:
{
"functions": ["makeGreeting"]
}| 关键点 | 说明 |
|---|---|
| 出口字段 | 结构化结果在 .structured_output,不在 .result |
| 适用场景 | 提取清单、打标签、出评分——下游是程序而不是人 |
示例 2:--allowedTools 放行写操作
默认的 -p 模式里,写类工具会被静默拒绝:进程正常结束、is_error 为 false,但文件一个字没变,被拦的调用记录在 permission_denials 字段里(本课实测)。要让 Claude 真动手,显式放行:
# 明确放行 Edit 工具,Claude 才能改文件
claude -p "在 README.md 末尾追加一行:由 AI 追加" --allowedTools "Edit"| 关键点 | 说明 |
|---|---|
| 只读命令例外 | git log、git status 等默认放行(实测无需授权即可执行,官方称 read-only command set) |
| 批量写场景 | 用 --permission-mode acceptEdits 整体放行文件写入(官方 headless 文档) |
| 安全边界 | 放行规则支持前缀匹配(如 Bash(git commit *)),规则语法详见 2.8 权限系统:少打断、不失控 |
练习
给你自己的真实项目写一个 claude -p 审查器——错别字、残留 console.log、commit message 规范,任选一个检查目标。要求:脚本接进 package.json(或 Makefile),并附上一次真实运行的输出。把脚本和输出归档进 claude-learning-lab/scripts/lint-claude/。
小结
开场那个问题——CI 里没有人点「允许」——现在有了答案:-p 让 Claude 无人值守地跑完就退出,JSON 输出让脚本读懂结果,--allowedTools 按需放权。记住三个关键词:管道化(stdin/stdout)、可解析(--output-format json)、显式放权(--allowedTools)。想一想:你的 CI 流水线里,哪个环节最适合先塞进一个 claude -p?它需要放行哪些工具?下一课把这套思路搬上 GitHub:在 PR 评论区直接 @claude。
常见报错
Not logged in · Please run /login(命令秒退,exit 1)
- 原因:加了
--bare。bare 模式跳过 OAuth 和钥匙串,认证只认ANTHROPIC_API_KEY(官方 headless 文档);订阅登录的机器必现(本课实测,0.8 秒即失败)。 - 解法:本地订阅机去掉
--bare;CI 环境配置ANTHROPIC_API_KEY后再加回--bare。
claude -p 说「已完成修改」,文件却一点没变
- 原因:写类工具(Edit/Write/Bash 写操作)默认被权限系统拦截,且不会报错——
is_error仍为 false(本课实测)。 - 解法:查 JSON 输出的
permission_denials字段确认被拦的工具,然后用--allowedTools "Edit"或--permission-mode acceptEdits放行;只读任务改用管道喂数据,绕开授权问题。
管道灌大文件时报错退出
- 原因:自 v2.1.128 起,stdin 输入上限 10MB,超限即报错并返回非零退出码(官方 headless 文档,未实测)。
- 解法:把内容写入文件,在提示词里引用文件路径让 Claude 自己读。
关联
- 5.1 worktree 并行与双会话模式(上一课)
- 5.3 GitHub Actions:在 PR 里 @claude(下一课:这套脚本化思路上云)
- 2.8 权限系统:少打断、不失控(--allowedTools 的规则语法出处)
- 1.4 Claude Code 如何工作:agent loop、内置工具与权限确认(-p 背后的同一个 agent loop)
- 官方文档:以编程方式运行 Claude Code(headless) · CLI 参数完整参考
- Last Updated:2026-07-03
- 适配版本:Claude Code 2.1.199(订阅登录,Linux)
- Sources:
- https://code.claude.com/docs/en/headless(-p 用法、管道、JSON 输出、--json-schema、--allowedTools、--bare、10MB stdin 上限、total_cost_usd)
- https://code.claude.com/docs/en/cli-reference(参数速查表)
- https://code.claude.com/docs/en/permissions#read-only-commands(只读命令默认放行集)
- https://code.claude.com/docs/en/best-practices(非交互模式与 fan-out 场景定位)
- 已验证日期:2026-07-03(Ubuntu 24.04 本机实测,全部预期输出为实测原文;过程记录见
生成记录/5.2-实测日志.md)