DeepSeek Harness 开了 Auto review 后工具全被取消:三类评审器配置排查
「切到 Auto review 之后,连 list_agents {} 这种无参数只读调用都被拦了」——当被拒的是根本不含风险的操作时,问题几乎一定不在风险策略,而在评审器自己。 社区在同一台机器上统计了 15 个会话、55 次真实评审故障,其中一次 /permission auto 之后 24 秒内 5 次调用全被拒(#10954)。这篇用一张对照表把三类故障分开,再逐条给处置。
DeepSeek Harness 先分清:三类「看起来像权限、其实是评审器配置」
三类故障的判别只看两样东西:报错里的错误码,以及被拒的调用有没有参数。错误码指向评审请求本身,无参数调用被拒则说明评审环节坏了。
| 类型 | 典型文案 | 触发条件 | 根因 | 处置 |
|---|---|---|---|---|
| 一、缺会话身份 | 400 MissingSessionID | 会话走 OpenCode Go 路由 | classifyRisk() 没传 sessionId | 换非 Auto 预设,或路由钉死头部 |
| 二、采样参数被写死 | invalid temperature: only 1 is allowed | 模型只接受 temperature=1 | 请求硬编码 temperature: 0 | 装 temperature guard 插件 |
| 三、技术失败当拒绝 | Auto review of tool … failed | 传输/配额/格式任一失败 | failed() 返回 kind: 'deny' | 切权限预设绕开,等上游改策略 |
判别顺序建议固定:先看错误码 → 再看会话路由 → 最后看是不是所有工具都挂。
DSH插件 第一类:评审请求没带 sessionId
OpenCode Go 要求每个推理请求都带 x-opencode-session,而评审请求没传 sessionId,provider 就不会补这个头,于是每次评审都在到达模型前被 400 掉。
- 看报错:
auto-review: reviewer ended with error INVALID_REQUEST: 400: {"type":"MissingSessionID", ...}。 - 确认会话模型走的是
opencode-go路由(社区案例是opencode-go/deepseek-v4.1-flash)。 - 直接验证依赖:向同一端点 POST,不带
x-opencode-session返回 400,带任意字符串返回 200。 - 知道链路:pi-ai 的
withOpenCodeSessionHeader()只在options.sessionId存在时注入;classifyRisk()组装GenerateOptions时没有这个字段。 - 对照其他辅助调用:主循环、压缩(
compaction-basic)、会话标题都传了sessionId,只有 auto-review 漏了。 - 现状:社区核对到 master 上
classifyRisk仍未补这一行,所以这不是「升级就好」的问题。 - 临时方案:把权限预设切到非 Auto(比如
never),或者在该路由配置里钉一个固定的headers.x-opencode-session,代价是失去按会话路由。
DeepSeek Harness 第二类:硬编码 temperature: 0
评审请求把 temperature: 0 深冻结后直接交给 ctx.llm.stream(),只接受 1 的模型直接回 400,而失败的评审会让每个工具调用在执行前被取消。
- 看报错:
auto-review: reviewer ended with error INVALID_REQUEST: 400: {"message":"invalid temperature: only 1 is allowed for this model", ...}。 - 确认会话模型属于「只接受
temperature=1」这一类(社区举例是 Kimi Code 的模型)。 - 理解位置:
auto-review/src/index.ts构造 options 时写死temperature: 0,序列化层只要字段存在就转发,OpenAI 兼容路由没有能力门控。 - 装社区插件:
dsh plugin --profile <profile> add github:argszero/dsh-auto-review-temperature-guard#v0.1.0。 - 在 profile 里加挂载项,条目名
@argszero/dsh-auto-review-temperature-guard;不填参数就省略temperature,让路由默认值生效,填了就发你指定的值。 - 验证:评审请求不再带 0,工具调用恢复执行;非评审请求必须原样透传,这是插件自带的断言之一。
插件用 system 是否以 REVIEW_POLICY 开头来识别评审请求,普通回合的 system 是 undefined,所以不会误伤对话请求。它的边界也明确:匹配不上就静默失效,属于 fail-open。
DeepSeek插件 第三类:技术失败被当成拒绝
评审器遇到传输错误、超时、配额、输出格式不对时统一返回 kind: 'deny',而这条路径发生在 await next() 之前,人工审批根本没机会介入。
- 看文案区分:
Auto review rejected tool …是真实判定,后面会走人工审批;Auto review of tool … failed是评审器失败,直接终结。 - 统计口径就用这个前缀,社区验证过它和拒绝文案不会混。
- 看代码:
failed()注释写着「作为独立错误而不是拒绝」,但返回的kind仍是'deny',区别只留在info里的AUTO_REVIEW_FAILED_ERROR_NAME。 - 旁边已有第三条路
askUser()(kind: 'ask'),社区建议把技术失败降级成「需要人确认」,并做成可配置,默认取ask。 - 还有两个加重因素:
parseDecision直接JSON.parse原文,不剥围栏、不抽取平衡括号、不做一次格式纠正重试;schemas.length !== 1时直接抛 schema 缺失,MCP 重连引起的工具集漂移就能触发。 - 临时方案:切到
danger-full-access或workspace-write绕开评审器,或换一条没有格式违规的路由——但这只是缓解,安全层的可靠性不该取决于你选了哪个聊天模型。
DeepSeek Harness 四步确认是「评审器坏了」而不是「风险策略太严」
先做一次对照实验:同一个工具、同一条命令,切掉 Auto 再跑。切掉就能过,说明被拦下的原因在评审环节,不在命令本身。
- 在会话里执行
/permission,切到never或workspace-write。 - 重跑刚才被拒的那条命令。能过,记下结论。
- 切回
auto,再跑一个无参数只读工具,比如列文件或列 agent。 - 如果无参数调用也被拒,直接判定为评审器故障;只有带写入/网络参数的命令被拒,才可能是真的风险判定。
这四步的价值在于排除法:它不需要读源码,也不需要抓包,一次就能把「策略问题」和「评审器问题」分开。
DSH插件 为什么三类都指向同一个设计问题
评审器复用会话自己的路由,包内没有 reviewer model 的配置项,安全闸的可靠性就被绑死在聊天模型上。
- 审查请求从最新的
request/header取 provider 和 model,用户换模型等于换评审器。 - 于是「换模型就正常」不是巧合,是结构决定的。
- 社区建议的长期形态是加配置:reviewer provider/model、最大重试次数、单次评审超时。
- 在那之前,任何一类故障的最快恢复都是先切权限预设,把会话救回来再排查。
注意事项
- 不要用「所有工具都被拒」来判断模型有风险。无参数只读调用被拒时,先怀疑评审器。
- 别把
Auto review of tool … failed当成模型拒绝。两者文案前缀不同,处理方式也不同。 - 装插件时固定版本号。
experimental-auto-review的 latest 标签曾指向更旧的构建,不锁版本可能复现出别的问题。 - 插件是缓解不是修复。硬编码的 0 和缺的那一行都还在源码里。
- 记录错误码与会话路由。这两项是区分三类故障的全部信息,缺一个就只能猜。
如果你的报错里既没有 MissingSessionID 也没有 invalid temperature,那大概率属于第三类,去会话日志里搜 AUTO_REVIEW_FAILED 确认。相关排查见 DeepSeek Harness 切 /permission auto 后工具全 ABORTED 怎么修。
常见问题
这是 DeepSeek Harness 的评审请求没带 sessionId。pi-ai 的 opencode-go provider 只有在拿到 options.sessionId 时才补 x-opencode-session 头,而 classifyRisk() 组装 GenerateOptions 时没传。社区核对 master 仍未补这一行,当前只能换非 Auto 预设或在路由上钉死这个头。
是 DeepSeek Harness 的评审器把 temperature: 0 写死了。auto-review/src/index.ts 构造请求时直接给 0 并深冻结,而只接受 1 的模型会回 400。社区插件 dsh-auto-review-temperature-guard 可以把它改成省略或指定值。
因为 DeepSeek Harness 的评审器故障分支里 failed() 返回的 kind 仍然是 'deny'。注释写的是把它当成独立错误而不是拒绝,但权限层只看 kind;旁边就有现成的 askUser()(kind: 'ask')可以降级给人确认,目前没有走。
看两类文案就能分清 DeepSeek Harness 的评审器是配置坏了还是真的判了风险:Auto review rejected tool … 是真实拒绝,会走人工审批;Auto review of tool … failed 是评审器自己失败,直接终结。后者出现在无参数只读调用上时,基本可以确定与风险无关。
因为 DeepSeek Harness 的评审器复用会话自己的 request/header 路由,包内没有 reviewer-model 覆盖项。安全层的可靠性被绑在聊天模型上:换到支持 temperature 1、也有会话路由的模型,两类 400 都会消失。
相关术语
- auto-review
- auto-review 是 DeepSeek Harness 的实验性权限预设,在工具执行前调用一次模型评审风险。评审不通过就拒绝;评审器本身失败时当前实现同样返回拒绝,且失败关闭(fail-closed)。— https://github.com/deepseek-ai/deepseek-harness/discussions/10954
- classifyRisk
- classifyRisk 是 auto-review 里组装评审请求的函数,它从会话最新的 request/header 取 provider 与 model。社区核对发现它既没传 sessionId,也把 temperature 固定成 0。— https://github.com/deepseek-ai/deepseek-harness/discussions/10643
- x-opencode-session
- x-opencode-session 是 OpenCode Go 要求的请求头,用于按会话做路由归属。pi-ai 的 provider 只在调用方给了 sessionId 时才补它,缺头会返回 400 MissingSessionID。— https://github.com/deepseek-ai/deepseek-harness/discussions/10643
- fail-closed
- fail-closed 是安全闸在自身故障时选择拒绝而非放行的策略。auto-review 采用 fail-closed,但把「评审器不可用」与「评审判定危险」映射成了同一个 deny 结果,导致用户无法区分。— https://github.com/deepseek-ai/deepseek-harness/discussions/10954
来源
- #10643 — Auto review fails every tool call when the session model routes through OpenCode Go· deepseek-ai(GitHub Discussions)
- #11157 — master 仍未补 sessionId 这一行· deepseek-ai(GitHub Discussions)
- #11196 — auto-review 硬编码 temperature: 0,只接受 1 的模型全部工具被拒· deepseek-ai(GitHub Discussions)
- #10954 — Reviewer technical failures return a terminal deny(no human fallback)· deepseek-ai(GitHub Discussions)