Summary
在调研 main 分支后发现,headless / non-interactive 模式(--prompt / -p、stdin 管道、CI / SDK 场景)下的失控保护仍然不完整。现有实现有一些兜底和统计能力,但缺少可配置的执行预算;在 --yolo / --approval-mode=yolo 这类无人值守场景中,风险更明显。
现状
已有能力
| 机制 |
位置 |
当前行为 |
| Session turn 限制 |
packages/cli/src/config/config.ts (--max-session-turns) / packages/core/src/config/config.ts |
默认 -1,表示 session 级无限制 |
单次 sendMessageStream 硬上限 |
packages/core/src/core/client.ts MAX_TURNS = 100 |
每次调用最多 100 轮,不是整个 headless run 的全局预算 |
| JSON usage 统计 |
packages/cli/src/nonInteractiveCli.ts / docs/users/features/headless.md |
可输出 token / tool usage 统计,但只是事后观测,不会中止运行 |
| SIGINT / SIGTERM |
packages/cli/src/nonInteractiveCli.ts |
支持外部中断 |
| Persistent retry opt-in |
QWEN_CODE_UNATTENDED_RETRY |
可在 429 / 529 时无限重试,适合 CI 保活,但需要额外预算限制配套 |
需要核实 / 修正的现有护栏
-
Loop detection 默认值不一致
文档 docs/users/configuration/settings.md 说 model.skipLoopDetection 默认是 false,即 loop detection 默认启用;但 packages/cli/src/config/settingsSchema.ts 的默认值是 true,packages/cli/src/config/config.ts 传给 core 的 fallback 也是 settings.model?.skipLoopDetection ?? true。这意味着 CLI 路径看起来默认跳过 loop detection。
这会影响本 issue 的风险判断:如果 CLI 默认确实是 skipLoopDetection=true,那 headless 下的重复行为兜底比文档描述更弱。
-
--yolo 与 sandbox 的文档 / 代码行为不一致
文档 docs/users/configuration/settings.md 写着 “Sandbox is enabled when using --yolo or --approval-mode=yolo by default”。但当前 loadSandboxConfig() 只读取 QWEN_SANDBOX、--sandbox 和 tools.sandbox,没有看到它根据 --yolo / approvalMode=yolo 自动开启 sandbox。
因此需要二选一:
- 如果产品期望是 yolo 自动启用 sandbox,则需要补实现和测试。
- 如果产品期望是 sandbox 独立配置,则需要修正文档,并在 headless + yolo + no sandbox 时给出明确告警。
缺失的护栏
- ❌ 无 wall-clock 超时:没有
--max-wall-time / --timeout 这类 run 级执行时长限制。
- ❌ 无 token / cost 上限:虽然 JSON 输出里能看到 usage,但无法设置累计 token 或费用预算,超过后自动中止。
- ❌ 无工具调用次数上限:缺少独立于 turn 的
--max-tool-calls / --max-api-calls 类限制。
- ❌ 无 API 速率 / quota 软保护:例如接近预算或 rate-limit 反复出现时降级、暂停或失败。
- ❌ headless + yolo 缺少默认风险提示:如果没有 sandbox,模型可自动执行 shell / edit / write 等高权限工具。
- ❌ 危险工具审计不够显式:headless + yolo 下应该更容易定位执行过哪些 shell / write / edit,尤其是 CI 日志和 JSON 输出。
实际风险
- 默认
maxSessionTurns = -1,用户不显式传 --max-session-turns N 时,session 级别没有总 turn 限制。
MAX_TURNS = 100 只是单次 sendMessageStream 的内部硬上限,不能替代 headless run 的全局预算。
QWEN_CODE_UNATTENDED_RETRY=1 会让 429 / 529 无限重试;这本身是有价值的 CI 能力,但如果没有 wall-clock / token / cost 上限,容易变成无人值守长时间运行。
--yolo / --approval-mode=yolo 自动批准所有工具。若 sandbox 没有实际启用,风险边界就是当前进程权限。
- loop detection 的默认值目前需要澄清;如果 CLI 默认跳过 loop detection,重复调用兜底比用户从文档理解的更弱。
建议改进方向
短期(低风险、先让行为可见):
中期(真正的 run 级预算):
参考位置
- Non-interactive 入口:
packages/cli/src/nonInteractiveCli.ts
- CLI flag 定义:
packages/cli/src/config/config.ts
maxSessionTurns 默认值:packages/core/src/config/config.ts
skipLoopDetection CLI fallback:packages/cli/src/config/config.ts
- settings schema:
packages/cli/src/config/settingsSchema.ts
- 单次 send loop 硬上限:
packages/core/src/core/client.ts
- sandbox 配置:
packages/cli/src/config/sandboxConfig.ts
- yolo 权限放行:
packages/cli/src/nonInteractive/control/controllers/permissionController.ts
如果方向 OK,我可以分阶段提 PR。建议第一阶段先处理两个“描述与实际行为不一致”的点:skipLoopDetection 默认值、yolo 与 sandbox 的关系;然后再补 headless 预算参数。
Summary
在调研
main分支后发现,headless / non-interactive 模式(--prompt/-p、stdin 管道、CI / SDK 场景)下的失控保护仍然不完整。现有实现有一些兜底和统计能力,但缺少可配置的执行预算;在--yolo/--approval-mode=yolo这类无人值守场景中,风险更明显。现状
已有能力
packages/cli/src/config/config.ts(--max-session-turns) /packages/core/src/config/config.ts-1,表示 session 级无限制sendMessageStream硬上限packages/core/src/core/client.tsMAX_TURNS = 100packages/cli/src/nonInteractiveCli.ts/docs/users/features/headless.mdpackages/cli/src/nonInteractiveCli.tsQWEN_CODE_UNATTENDED_RETRY需要核实 / 修正的现有护栏
Loop detection 默认值不一致
文档
docs/users/configuration/settings.md说model.skipLoopDetection默认是false,即 loop detection 默认启用;但packages/cli/src/config/settingsSchema.ts的默认值是true,packages/cli/src/config/config.ts传给 core 的 fallback 也是settings.model?.skipLoopDetection ?? true。这意味着 CLI 路径看起来默认跳过 loop detection。这会影响本 issue 的风险判断:如果 CLI 默认确实是
skipLoopDetection=true,那 headless 下的重复行为兜底比文档描述更弱。--yolo与 sandbox 的文档 / 代码行为不一致文档
docs/users/configuration/settings.md写着 “Sandbox is enabled when using--yoloor--approval-mode=yoloby default”。但当前loadSandboxConfig()只读取QWEN_SANDBOX、--sandbox和tools.sandbox,没有看到它根据--yolo/approvalMode=yolo自动开启 sandbox。因此需要二选一:
缺失的护栏
--max-wall-time/--timeout这类 run 级执行时长限制。--max-tool-calls/--max-api-calls类限制。实际风险
maxSessionTurns = -1,用户不显式传--max-session-turns N时,session 级别没有总 turn 限制。MAX_TURNS = 100只是单次sendMessageStream的内部硬上限,不能替代 headless run 的全局预算。QWEN_CODE_UNATTENDED_RETRY=1会让 429 / 529 无限重试;这本身是有价值的 CI 能力,但如果没有 wall-clock / token / cost 上限,容易变成无人值守长时间运行。--yolo/--approval-mode=yolo自动批准所有工具。若 sandbox 没有实际启用,风险边界就是当前进程权限。建议改进方向
短期(低风险、先让行为可见):
model.skipLoopDetection的文档 / schema / CLI fallback 默认值不一致问题。--yolo与 sandbox 的关系:要么实现 yolo 默认启用 sandbox,要么修正文档。--max-session-turns+ sandbox + JSON usage logging。中期(真正的 run 级预算):
--max-wall-time <duration>:超过执行时长后中止并返回结构化错误。--max-tool-calls N/--max-api-calls N:限制工具调用和模型请求次数。--max-tokens N:基于累计 usage 中止运行。--max-budget-usd N或 provider-neutral budget hook:能接入不同 provider 的价格模型时再精确计费。参考位置
packages/cli/src/nonInteractiveCli.tspackages/cli/src/config/config.tsmaxSessionTurns默认值:packages/core/src/config/config.tsskipLoopDetectionCLI fallback:packages/cli/src/config/config.tspackages/cli/src/config/settingsSchema.tspackages/core/src/core/client.tspackages/cli/src/config/sandboxConfig.tspackages/cli/src/nonInteractive/control/controllers/permissionController.ts如果方向 OK,我可以分阶段提 PR。建议第一阶段先处理两个“描述与实际行为不一致”的点:
skipLoopDetection默认值、yolo与 sandbox 的关系;然后再补 headless 预算参数。