Claude Code 学习站
Mingyu's Library

学习站 / 系统课程 / D13

D13 · Headless 与 CI

把 Claude Code 从「你盯着的终端会话」变成「流水线里的一条命令」:claude -p 无头运行、结构化输出、无人值守的权限收口,以及官方 GitHub Action 的接法。

前十二课里 Claude Code 都有一个人坐在终端前拍板;本课把人撤掉。你会学到 headless 模式(claude -p)的输入输出契约——prompt 进、文本或 JSON 出、退出码定成败——以及没人按「允许」时权限怎么办,最后用官方 GitHub Action 把它接进 PR 流程。

为什么这天学这个

本课是前面几课的「无人值守版」总装:D3 权限模式里你学过交互式的允许/拒绝,而 CI 里没有人回答提示,答案只能预先写进 --allowedTools 或权限模式;D8 Hooks 里的「机器裁判」思路在 CI 中同样成立——无头运行时,hooks 是少数还能强制拦截动作的机制。本站观点

往后看,D14 Plugins 讲打包分发,而 headless 正是分发后的主要消费场景之一:官方 action 支持在 CI 里装插件、跑插件里的 skill。学完本课再去读实战 Tip F3 · GitHub Actions 集成F4 · 定时任务,就是从「懂机制」到「照着抄」的一步。

概念讲清楚

headless:一条命令的输入输出契约

官方给任何 claude 命令加上 -p(即 --print)就进入非交互模式:执行完 prompt、打印结果、退出,所有 CLI flag 都可以和 -p 组合。它读 stdin,所以能像普通 Unix 工具一样接管道(piped stdin 自 v2.1.128 起上限 10MB):

# 问答式:结果打到 stdout
claude -p "What does the auth module do?"

# 管道式:构建日志进,解释出
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt

官方成败用退出码表达:成功退出码 0,运行失败非 0,脚本可以直接对退出状态做分支;被 SIGTERM 终止时中止当前轮次、跑完 SessionEnd hooks 后以 143 退出。官方CI 里推荐加 --bare:跳过 hooks、skills、插件、MCP、auto memory 和 CLAUDE.md 的自动发现,保证每台机器结果一致、启动更快;官方说明它将来会成为 -p 的默认行为。注意 bare 模式不读 OAuth 凭据和系统钥匙串,必须通过环境变量 ANTHROPIC_API_KEY(或 settings 里的 apiKeyHelper)鉴权。

三种输出格式:给人看 vs 给程序看

--output-format输出适用
text(默认)纯文本回答人看、简单管道
json单个 JSON:resultsession_idtotal_cost_usd 及分模型成本脚本解析、逐次记账
stream-json逐行 JSON 事件流(配 --verbose --include-partial-messages 可拿到 token 级增量)实时进度、自建 UI

官方要输出符合固定 schema 的结构化结果,用 --output-format json--json-schema,结果落在 structured_output 字段里,配 jq 就能直接取值;schema 非法时报错退出(v2.1.205 起)。官方stream-json 流的第一个事件 system/init 带有 mcp_server_errorsplugin_errors 等字段——CI 可以对「非空错误数组」直接判失败,抓住那种服务器没加载成功但进程仍正常退出的静默故障。

无人值守时,权限怎么办

这是 D3 问题的 CI 解法。交互式会话里权限提示由人回答;headless 里没人在,官方给了三层收口:官方

  • 逐条放行:--allowedTools "Bash(git diff *),Read,Edit",用权限规则语法精确到命令前缀。注意 * 前那个空格:Bash(git diff *) 只放行 git diff 开头的命令,写成 git diff* 会连 git diff-index 一起放行。
  • 整体基线:--permission-mode dontAsk 拒绝一切不在 allow 规则或只读命令集内的动作,适合锁死的 CI;--permission-mode acceptEdits 免提示写文件并自动放行 mkdir、touch、mv、cp 等常见文件系统命令,其余 shell 命令仍需 allow 规则,否则运行中止。
  • 止损上限:--max-turns 限制轮次(CLI 默认无上限,达到即报错退出)、--max-budget-usd 限制单次美元花费(subagent 的花费也计入,上限强制行为需 v2.1.217+)。
误区澄清「CI 没人看,干脆 --dangerously-skip-permissions 全放开」——省事但等于把仓库和 runner 的全部权限交给模型输出,一旦 prompt 被 PR 内容注入就没有任何闸门。CI 里更稳的组合是:精确的 allowedTools + dontAsk/acceptEdits 基线 + max-turns/预算上限,再用 D8 的 hooks 做硬拦截。本站观点

接进 GitHub Actions

官方官方提供 GitHub Action anthropics/claude-code-action@v1(底层就是 Agent SDK / headless 能力)。它会自动检测运行模式:配置了事件触发但没写 prompt,就响应评论里的 @claude 提及(交互模式);写了 prompt 输入,就直接执行(自动化模式)。任何 CLI 参数都能通过 claude_args 透传,比如 --max-turns(action 场景默认 10)、--model--allowedTools

@claude 评论提及 pull_request 事件 schedule(cron) GitHub Actions runner(CI) claude-code-action@v1 headless 运行 claude -p 鉴权:secrets.ANTHROPIC_API_KEY 注入,权限由 claude_args 收口 PR 评论 / 代码审查 新提交 / 新 PR JSON 结果与退出码
三类触发事件进入同一个 runner:官方 action 以 headless 方式运行 Claude,产出评论、提交或供后续步骤消费的 JSON。

官方鉴权:最快路径是在 Claude Code 终端里跑 /install-github-app(需仓库 admin,GitHub App 要求 Contents/Issues/Pull requests 的读写权限,快捷方式仅限直连 Claude API 的用户);手动路径是装 App、把 API key 存进仓库 secret ANTHROPIC_API_KEY、从官方示例拷 workflow。订阅用户可用 claude setup-token 生成长效 OAuth token 给 CI 使用。企业环境可走 Amazon Bedrock / Google Cloud,用 OIDC 免存静态密钥,action 侧开 use_bedrockuse_vertex永远不要把 key 写进 workflow 文件。

官方成本:CI 里花的是两笔钱——GitHub Actions 分钟数 + API token。官方给的控法:用具体明确的 @claude 指令减少无效调用、--max-turns 防止无限迭代、workflow 级 timeout 防失控任务、GitHub concurrency 控制并发数。配合 --output-format json 里的 total_cost_usd,可以逐次记账。

当天能做完的实操

前三步只需要本机装好 Claude Code,约 15 分钟;第四步需要一个你有 admin 权限的 GitHub 仓库。

  1. 跑一次最小 headless 调用并看退出码。在任意项目目录执行下面第一条命令,预期看到一个 JSON,里面有 result(回答文本)、session_idtotal_cost_usd;紧接着 echo $? 应输出 0
  2. 把它当 Unix 工具用。跑第二条管道命令,预期 Claude 只输出 diff 里的拼写问题(没有多余寒暄)——这就是「Claude 作为 linter」的雏形,不给 Bash 权限也能读到 diff,因为内容是从管道进来的。
  3. 体验权限收口。第三条命令用 acceptEdits 基线跑一个会改文件的任务,预期不弹任何确认;然后把 --permission-mode acceptEdits 去掉重跑,预期任务因为没人批准写文件而无法完成——这就是 CI 里必须预先声明权限的原因。
# 1. 最小 headless 调用 + 退出码
claude -p "用一句话总结这个项目是做什么的" --output-format json
echo $?

# 2. 管道:diff 进,拼写检查出
git diff main | claude -p "你是拼写检查器。对 diff 中每个拼写错误,输出一行 文件:行号,下一行写问题。没有其它输出。"

# 3. 权限收口:先带基线跑,再去掉基线对比
claude -p "把 README.md 里的错别字修掉" --permission-mode acceptEdits --max-turns 5
  1. 接进 GitHub Actions。在有 admin 权限的仓库目录里进入交互式 Claude Code,运行 /install-github-app,按引导装 App、加 secret、生成 workflow;完成后在任一 issue 或 PR 评论里发 @claude 这个仓库的测试覆盖薄弱点在哪?,预期几分钟内 Claude 以评论回复。没有合适仓库的话,今天先把下面这个最小 workflow 读懂,明天再装:
name: Claude Code
on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
jobs:
  claude:
    runs-on: ubuntu-latest
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          claude_args: "--max-turns 10"

验收标准

  • 能向别人解释 claude -p 的输入输出契约:prompt/stdin 进,text/json/stream-json 出,退出码 0 或非 0 定成败。
  • 能说出 json 与 stream-json 各适合什么场景,以及 --json-schema 的结果落在哪个字段。
  • 实操 1–3 全部跑通,并能解释第 3 步去掉权限基线后行为为什么变了。
  • 能说出 CI 鉴权的三条路:ANTHROPIC_API_KEY secret、订阅用户的 claude setup-token、Bedrock/Vertex 的 OIDC。
  • 自测题:CI 里要无人值守地让 Claude 修 lint 并提交,既不想弹任何提示、又不想全放开权限,该怎么配?(要点:--allowedTools 精确放行 git/lint 命令 + acceptEditsdontAsk 基线 + --max-turns/--max-budget-usd 止损;而不是 --dangerously-skip-permissions。)