无人值守时没有人在旁边看着,约束只能靠参数表达。 四个维度:输出格式、成本护栏、工具权限、执行控制。 最容易踩的两个坑:「语法对、语义荒谬」的值不报错,以及同一项既允许又禁止——禁用优先, 你以为开了,其实没开。
上一节说了模式怎么变,这一节说变了之后靠什么约束它。 这一节有真跑:写了一个参数解析器,六组参数逐个跑,看哪一组会被拒、哪一组悄悄变形。
一、四个维度各管什么
输出格式:给人看还是给程序看。text、json、stream-json 三种,决定了能不能接管道。 成本护栏:最多跑几轮就停。它防的不是「跑错」,是「跑不完还一直跑」。 工具权限:哪些工具能碰。这是最小权限原则唯一能落地的地方。
执行控制:用哪个模型、系统提示写什么、输出要什么形状。 四个维度的顺序有讲究:先定输出格式(决定下游怎么接),再定权限(决定它能做什么),最后才是护栏。
二、六组参数真跑一遍
演示里六组:只有输出格式、加成本护栏、护栏设成零、允许与禁止同一项、拼错的参数名、要结构化输出。 只有拼错的那组报错,退出码 2。其余五组全部「成功」——但其中两组的结果不是你要的。 护栏设成零那组:参数合法,含义是「一轮都不许跑」。它不会报错,只会让任务什么都没干就结束。
允许与禁止同一项那组:解析出来的可用列表里只剩 Read,Edit 被悄悄拿掉了。
三、为什么「合法但荒谬」最危险
因为它不报错。你在日志里看到退出码零、看到一段输出,会以为它干完了。 而实际上它一轮都没跑,或者一个工具都没调。 这类值有一个共同特征:语法上完全合法,语义上和你的意图相反。
防法不是靠参数校验,是靠「跑完之后核对结果」——比如检查它到底改了几个文件。
四、输出格式决定能不能接管道
text:人读的段落,不能接管道。只能在本地看一眼。 json:一整块结果,跑完才有。能取结论、能取用量。 stream-json:一行一个事件,能边跑边读。长任务和 CI 日志用它。
选了 text 又想用 jq 取字段,就只能回去改参数——这是流水线里最常见的返工。 所以第一版配置就把格式定成 json,哪怕现在只是本地跑。
五、权限参数的坑
允许列表和禁止列表同时给的时候,禁用优先。这是安全上正确的选择,但很反直觉。 你写了允许 Edit,又在禁止里顺手写了 Edit,结果是「没有 Edit」,而且不报错。 做法是只写一边:要么列允许(白名单),要么列禁止(黑名单)。两个都写就要自己对一遍。
这个坑和 6.8 节那条「权限要收窄」是一路的:收窄的规则越简单,越不会被自己绕进去。
六、动手 Demo:四组参数,哪些互斥、哪些会被悄悄盖掉
headless 模式下没有人在旁边看着,所以「约束」只能靠参数表达。 四条维度的参数,六组组合逐个真跑一遍。
fmt, maxturn, allow, deny = u"text", None, [], []
eff = [t for t in allow if t not in deny] # 禁用优先
print(json.dumps({"fmt": fmt, "maxturn": maxturn, "eff": eff}))
实跑输出:
--------------------------------------------------------------------
四组参数,哪些互斥、哪些会被悄悄盖掉
--------------------------------------------------------------------
headless 模式下没有人在旁边看着,所以「约束」只能靠参数表达。
四条维度的参数:输出格式、成本护栏、工具权限、执行控制。
① 四个维度各管什么
--------------------------------------------------------------------
| 维度 | 代表参数 | 管什么 | 常见误用 |
|---|---|---|---|
| 输出格式 | --output-format | 给人看还是给程序看 | 流水线里用了 text |
| 成本护栏 | --max-turns | 最多几轮就停 | 不设,跑到超时 |
| 工具权限 | --allowed-tools | 哪些工具能碰 | 给了一堆用不上的 |
| 执行控制 | --model、schema | 用哪个模型、输出什么形状 | schema 和 text 同时要 |
② 六组参数真跑一遍
--------------------------------------------------------------------
| 这组参数 | 退出码 | 解析要点 |
|---|---|:---:|
| 只有输出格式 | 0 | fmt=json turns=None eff=无 |
| 加成本护栏 | 0 | fmt=json turns=8 eff=无 |
| 护栏设成零 | 0 | fmt=text turns=0 eff=无 |
| 允许与禁止同一项 | 0 | fmt=text turns=None eff=Read |
| 拼错的参数名 | 2 | unknown argument: --output-formats |
| 要结构化输出 | 0 | fmt=json turns=None eff=无 |
第三组最值得看:轮数上限设成零,参数本身合法,但它意味着「一轮都不许跑」。
这类「语法对、语义荒谬」的值不会报错,只会让任务什么都没干就结束。
第四组是权限的坑:同一项既允许又禁止。真实实现里禁用优先——所以你以为开了 Edit,其实没开。
③ 输出格式决定能不能接管道
--------------------------------------------------------------------
| 格式 | 长什么样 | 能流式读吗 | 什么时候用 |
|---|---|---|---|
| text | 人读的段落 | 不能 | 本地看一眼 |
| json | 一整块结果 | 能,跑完才有 | 取结论、取用量 |
| stream-json | 一行一个事件 | 能,边跑边有 | 长任务、CI 日志 |
选了 text 又想用 jq 取字段,就只能回去改参数——这是流水线里最常见的返工。
④ 成本护栏该设在哪
--------------------------------------------------------------------
不要设「一次跑完」的上限,要设「跑到这儿就停」的上限:轮数、时间、花费。
因为无人值守的失败模式不是「跑错」,是「跑不完还一直跑」。
护栏的另一个作用是把失败变早:八轮就停,比三十分钟超时好查得多。
! 参数名与取值以各版本实际帮助为准,这里演示的是它们的分工,不是逐字背诵的命令。
①四个维度:输出格式(给人看还是给程序看)、成本护栏(最多几轮)、工具权限(哪些能碰)、 执行控制(模型与输出形状)。 ②六组里只有拼错参数名那组报错,退出码 2。其余五组全部「成功」,但其中两组的结果不是你要的。 ③护栏设成零那组最值得看:参数合法,含义是「一轮都不许跑」。它不报错,只会让任务什么都没干就结束。 这类「合法但荒谬」的值有一个共同特征:语法上完全合法,语义上和你的意图相反。 防法不是参数校验,是跑完核对结果。 ④允许和禁止同时给同一项时,禁用优先。 你写了允许 Edit 又在禁止里写了 Edit,结果是没有 Edit,而且不报错。 做法是只写一边:要么白名单,要么黑名单,两个都写就要自己对一遍。
30 秒实验
- 把护栏从八轮改成两轮,重跑一遍。参数照样合法,但任务会怎样结束? 这种情况该看哪个信号判断它「没干完」?
- 给输出格式加上一个不存在的值(比如 yaml),重跑一遍。它会当场报错还是静默退回默认? 这说明了什么?
- 把禁止列表整个删掉,只留允许列表,重跑第四组。结果变了吗?为什么「只写一边」更不容易出错?
常见误区
- 只设成本护栏不设权限。护栏防的是花钱,权限防的是它把不该动的东西动了。
- 允许和禁止同时写,还以为是「取交集」以外的语义。禁用优先,而且不报错。
- 输出格式留默认的 text,下游却用 jq 解析。第一次改需求就得返工。
- 把「语法合法」当成「配置正确」。护栏设成零也能跑,只是它什么都没干。
- 以为参数只是开关。在无人值守里,参数是唯一的约束手段,写错就等于没约束。
自测三问(点开对答案)
1. 四个维度分别是什么? —— 输出格式(能不能接管道)、成本护栏(最多跑几轮)、工具权限(能碰什么)、 执行控制(模型与输出形状)。 2. 「合法但荒谬」的参数值为什么危险? —— 因为它不报错。你会看到退出码零和一段输出,以为它干完了,实际上它什么都没干。 只能靠跑完核对结果来发现。 3. 允许列表和禁止列表同时给了同一项,哪个生效? —— 禁止生效。而且不报错——你以为开了,其实没开。所以最好只写一边。参数会设了,下一节把它接进命令行:一条管道把 agent 当普通命令用。
- ☐ (本篇完,下一篇 7.3 Unix 管道:将 Claude 融入命令行工作流)
相关笔记
- 安全机制:权限为什么要收窄
- Unix 管道:把 agent 当普通命令用
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引