avatar


7.Headless

简介

Claude Code的本质是"大模型+工具调用循环+有限上下文窗口",到目前为止,工具调用循环(agent loop)的能力边界已经补齐,但循环的每一轮仍然需要人守着终端——回答权限提示、阅读输出、发起下一条指令;上一章《6.MCP》把工具集扩展到了本地之外,能力齐了,人却还没退场。

CI/CD流水线、定时任务、批处理脚本里没有这个人。把人移出循环,要回答三个子问题:

  • 输入输出机器化
    没有人读屏幕。输入要能从管道来,输出要能被脚本解析。
  • 权限与运行边界预先声明
    没有人点确认,也没有人临场把握预算。哪些工具能用、花多少钱、跑多少轮、用什么模型,必须在启动前说清楚。
  • 会话可脚本化
    没有人记着上下文。多步任务之间的衔接,要靠脚本传递会话。

Claude Code的答案是Headless模式(非交互模式):加上-p参数,Claude Code不启动交互界面,执行完任务、把结果写到标准输出、退出进程。本章按这三个子问题展开,最后落到CI/CD实战。

基本用法与输出格式

-p--print的短写,表示非交互执行:返回结果后进程退出,不等待任何用户输入。

示例代码:

1
claude -p "总结这个仓库的构建方式"

prompt也可以从标准输入来。Claude Code能直接接在Unix管道后面,像grepjq一样成为工具链中的一环。

示例代码:

1
tail -200 app.log | claude -p "找出这段日志里的异常"

交互模式的CLI参数在Headless模式下同样适用;另有一批参数只配合-p才有意义,例如--output-format--max-turns--json-schema--no-session-persistence

输出这一侧由--output-format控制,共三种格式:textjsonstream-json

text与json

text是默认格式,直接输出纯文本响应,适合人读,或者直接重定向到文件。

json把最终结果连同执行元数据一起输出为一个JSON对象,是脚本消费的首选。关键字段:

字段 说明
result 最终文本响应
session_id 会话ID,可传给--resume继续这次会话
total_cost_usd 本次运行的总费用(美元)
model 实际使用的模型
num_turns 实际执行的轮数
stop_reason 停止原因,可能值有end_turnmax_tokenstool_usestop_sequence
structured_output 结构化输出,仅在使用--json-schema时存在

返回的JSON形如:

1
2
3
4
5
6
7
8
{
"result": "……",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"total_cost_usd": 0.0123,
"model": "……",
"num_turns": 3,
"stop_reason": "end_turn"
}

配合jq取字段,是Headless脚本的基本功。

示例代码:

1
claude -p "统计这段日志里各类错误出现的次数" --output-format json | jq -r '.result'

result毕竟还是一段自然语言。如果下游程序需要固定结构的数据,用--json-schema传入一个JSON Schema,最终输出会按Schema组织,出现在返回对象的structured_output字段里,不必再用正则去猜文本格式。

示例代码:

1
2
3
4
tail -200 app.log | claude -p "按类别统计错误数量" \
--output-format json \
--json-schema '{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"count":{"type":"number"}}}}},"required":["errors"]}' \
| jq '.structured_output'

stream-json

长任务不适合等到最后一次性拿结果。--output-format stream-json逐行输出JSON事件,每行一个完整的JSON对象,可以边执行边消费。

注意:-p下使用stream-json输出必须同时指定--verbose

主要事件类型:

事件 说明
systemsubtypeinit 会话元数据:session_id、模型、已加载插件等
systemsubtypeapi_retry API重试信息
systemsubtypeplugin_install 插件安装进度,需设置环境变量CLAUDE_CODE_SYNC_PLUGIN_INSTALL
user_message 用户消息
assistant_message 助手消息,附带stop_reasonusage(token用量)
tool_result 工具执行结果
stream_event token级增量文本流,需另加--include-partial-messages

围绕行协议还有三个参数:

  • --include-partial-messages
    输出token级的中间流事件,须与-p --output-format stream-json --verbose同用。
  • --include-hook-events
    把Hook(钩子)生命周期事件也写进事件流,须与--output-format stream-json同用。
  • --input-format stream-json
    输入侧也用行协议,调用方可以向同一个进程持续发送用户消息,须配合--output-format stream-json。在此基础上,--replay-user-messages会把用户消息回显到stdout,便于审计。

运行控制参数

人在场时,花多少钱、跑多少轮、用什么模型,都是临场把握的;人退场后,这些都要变成启动前的参数。

分组 参数 说明
预算与轮数 --max-turns 限制最大交互轮数,超限时以错误码退出
预算与轮数 --max-budget-usd 设定本次运行的费用上限(美元),达到即停止
模型 --model 指定模型,接受别名(sonnetopushaikufable)或完整模型ID
模型 --fallback-model 主模型超载时自动降级,接受逗号分隔的降级链,覆盖settings中的fallbackModel
系统提示 --append-system-prompt--append-system-prompt-file 在默认系统提示之后追加内容
系统提示 --system-prompt--system-prompt-file 完全替换系统提示
极速启动 --bare 跳过各类扩展的自动发现,最小化启动
配置 --settings 临时覆盖设置,接受JSON文件路径或行内JSON字符串
配置 --mcp-config 显式加载MCP服务器,接受JSON文件或行内字符串,空格分隔多个
配置 --strict-mcp-config 只使用--mcp-config指定的服务器,忽略其他来源的MCP配置
缓存 --exclude-dynamic-system-prompt-sections 把工作目录、环境信息等动态段移入首条用户消息,改善跨用户、跨机器的prompt cache命中率

其中三处值得展开。

追加还是替换系统提示。 --append-system-prompt--system-prompt的差别不在语法,在责任。追加保留Claude Code默认系统提示的全部内容——工具使用方式、安全约束、输出习惯——只在末尾加上我们的要求,适合绝大多数场景。替换则把默认内容整个删掉,模型如何正确使用工具、如何守住安全边界,从此由我们自己负责。优先级上,CLI标志高于--settings传入的值,再高于settings.json里的配置。

--bare的取舍。 --bare做极速启动:跳过Hooks、Skills(技能)、插件、MCP(Model Context Protocol,模型上下文协议)服务器、自动记忆和CLAUDE.md的自动发现,工具只保留内置的BashReadEditCLAUDE.md的加载机制见《2.Memory》,Skills的发现机制见《3.Skills》,MCP的配置来源见《6.MCP》。收益是启动快、行为可预测、上下文干净;代价是项目约定全部丢失,prompt必须自带完整上下文。确有需要时,可以在--bare基础上用--mcp-config显式补回MCP服务器。

环境变量。 Headless场景下常用的一组:

环境变量 说明
ANTHROPIC_API_KEY API密钥,优先于订阅认证
ANTHROPIC_MODEL 默认模型,被--model覆盖
API_TIMEOUT_MS API请求超时,默认600000毫秒
BASH_DEFAULT_TIMEOUT_MS 长时间运行的Bash命令超时,默认120000毫秒
CLAUDE_CODE_SKIP_PROMPT_HISTORY 禁用会话持久化,等同--no-session-persistence
CLAUDE_CODE_SIMPLE 标记裸模式运行,由--bare自动设置
CLAUDE_CODE_SYNC_PLUGIN_INSTALL 启用plugin_install事件流,输出插件安装进度
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 后台代理等待上限(毫秒),默认600000,0表示无限

无人值守的权限

交互模式下,权限系统的最后一道保险是弹给人的确认提示;-p下这道提示无处可弹。权限规则的语法(Tool(specifier))、六种权限模式的定义、deny高于ask高于allow的优先级,都在《1.概述》,本节讲无人值守场景如何把决策预先声明出来,并补充最容易写错的Bash规则匹配细节。声明的手段有四个:

  • --allowedTools
    白名单。命中的工具调用直接放行,不再提示。逗号分隔多条规则。
  • --disallowedTools
    黑名单。支持*通配符,匹配的工具被移除或调用被阻止。
  • --permission-mode
    直接指定权限模式,取值defaultacceptEditsplanautodontAskbypassPermissions。只读的分析类任务可以直接给plan
  • --dangerously-skip-permissions
    跳过权限提示,等同--permission-mode bypassPermissions

--dangerously-skip-permissions名副其实:确认环节整个消失,Claude能调用的工具就是它会调用的工具。只在严格隔离的环境里使用,比如一次性容器、不含生产凭据的CI runner。即便在这种模式下,显式的ask规则和针对root、home目录的删除操作仍不会被静默放行;管理员也可以在Managed设置里用disableBypassPermissionsMode彻底锁死这个参数。

白名单里最常用也最容易写错的是Bash规则。空格是分词符,*按词通配。

示例代码:

1
2
3
Bash(npm run build)     # 只匹配这一条命令
Bash(npm run test *) # 匹配npm run test加任意参数
Bash(git *) # 匹配任意git子命令

注意:Bash(ls *)Bash(ls*)是两条不同的规则。前者把ls当作完整的命令名,匹配ls带任意参数;后者是字符级前缀,可能命中以ls开头的其他命令。另外,用&&等连接符拼起来的复合命令会按子命令拆开分别评估,写白名单时每一段都要覆盖到。

会话脚本化

-p每次调用默认开启一个全新会话,执行完保存到磁盘后退出。多步任务的衔接靠三个参数:-c--continue)继续最近一次会话;--resume按会话ID或名字恢复指定会话;--fork-session在恢复时分叉出新的会话ID,源会话保持不变。串联多步的标准做法,是用--output-format json拿到session_id,传给下一步的--resume

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 第一步:分析日志,记下会话ID
session_id=$(tail -200 app.log \
| claude -p "找出这段日志里的异常,定位根本原因" --output-format json \
| jq -r '.session_id')

# 第二步:在同一会话里追问,上一步读过的内容仍在上下文中
claude -p "针对刚才定位的根本原因,给出修复建议" --resume "$session_id"

# 第三步:从同一份分析分叉出另一条路径,原会话不受影响
claude -p "如果暂时不改代码,有哪些临时缓解手段" \
--resume "$session_id" --fork-session

# 简便写法:不记ID,直接续上最近一次会话
claude -p "把上面的结论整理成一段值班记录" -c

不需要留痕的一次性调用,加--no-session-persistence:会话不写盘,也无法再恢复。等效的环境变量是CLAUDE_CODE_SKIP_PROMPT_HISTORY

注意:--resume只在当前项目及其git worktrees里查找会话,换个目录就找不到了。跨目录的流水线要么固定工作目录,要么放弃会话衔接、在prompt里显式传递上一步的结论。

CI/CD实战

把前面的参数组合起来,就是CI/CD里的四种常见形态。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 一、日志分析:定时任务里跑,只读、极速启动
tail -200 app.log | claude -p "找出日志中的异常并总结" \
--bare --allowedTools "Read"

# 二、PR安全审查:只给读权限,结果交给jq
git diff main --name-only | claude -p "对这些文件做安全审查,指出风险点" \
--allowedTools "Read" --output-format json | jq -r '.result'

# 三、自动提交:Bash白名单精确到git子命令
claude -p "检查已暂存的变更,写一条符合规范的提交信息并提交" \
--allowedTools "Bash(git diff *),Bash(git commit *),Bash(git status *)" \
--bare

# 四、流式监控:长任务边跑边看
claude -p "分析本次构建日志,找出失败原因" \
--output-format stream-json --verbose --include-partial-messages \
| jq 'select(.type == "stream_event")'

在GitHub Actions上不必手写这些命令,官方提供了anthropics/claude-code-action@v1

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: claude-review
on:
pull_request:

jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "审查本次PR的代码变更,重点检查安全问题,按严重程度输出报告"
claude_args: "--max-turns 5 --allowedTools Read,Grep"

关键字段:

字段 说明
prompt 任务指令;留空时Action转为监听评论中的触发词
claude_args 透传任意CLI参数,空格或换行分隔,本章的参数全部适用
plugins 插件名@市场名格式安装插件
trigger_phrase 自定义触发词,默认@claude

注意:从Beta版(v0.x)升级到v1时,direct_prompt改名为promptcustom_instructions改为经claude_args--append-system-prompt,原先的mode字段改为自动检测。

经验

  • --bare下必须显式提供ANTHROPIC_API_KEY
    极速启动跳过了keychain与OAuth凭据的读取,缺了这个环境变量会直接认证失败。
  • stream-json的参数是联动的
    -p下要配--verbose--include-partial-messages在此基础上才生效;--replay-user-messages要求--input-format--output-format都是stream-json。漏掉其中一个,症状往往是"没有报错但拿不到想要的事件"。
  • structured_output不是常驻字段
    只有给了--json-schema它才存在,下游脚本取值前先判空。
  • 交互式命令在-p下不可用
    /login这类需要对话框的命令无法执行,凭据改用环境变量或--settings传入。不过prompt里可以直接写/skill-name调用Skill,见《3.Skills》
  • 交互专属的Hook要改挂载点
    PermissionRequest这类只在交互模式下工作的Hook,在Headless下改挂到PreToolUse上,协议见《5.Hooks》
  • 权限声明宁细勿粗
    优先写白名单、少用黑名单,Bash规则精确到子命令,--dangerously-skip-permissions只留给真正隔离的环境。

最后是Headless模式的天花板:它是进程外的整只CLI,控制粒度到参数为止,进出都是字符串。当我们需要进程内的控制——把自定义工具注册进循环、拿到类型化的消息对象、用代码逐次决策权限——就该换Claude Agent SDK了,下一章《8.SDK》展开。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/128070
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板