DeepSeek Harness auto-review cancels every tool: config fix
"After switching to Auto review, even a no-argument read-only call like list_agents {} gets blocked" — when the call being denied contains no risk at all, the problem is almost never the risk policy but the reviewer itself. On one machine the community counted 15 sessions and 55 genuine review failures, including 5 calls denied within 24 seconds of running /permission auto (#10954). This article uses one comparison table to separate three fault types, then handles each in turn.
DeepSeek Harness: First, separate three "looks like permissions, actually reviewer config" faults
Judging the three faults needs only two things: the error code in the message, and whether the denied call had arguments. An error code points at the review request itself; a denied no-argument call means the review stage is broken.
| Type | Typical message | Trigger | Root cause | Handling |
|---|---|---|---|---|
| 1. Missing session identity | 400 MissingSessionID | session routed through OpenCode Go | classifyRisk() passes no sessionId | Switch to a non-Auto preset, or pin the header on the route |
| 2. Sampling parameter hardcoded | invalid temperature: only 1 is allowed | model only accepts temperature=1 | request hardcodes temperature: 0 | Install the temperature guard plugin |
| 3. Technical failure treated as denial | Auto review of tool … failed | any transport/quota/format failure | failed() returns kind: 'deny' | Switch permission preset to bypass, wait for upstream policy change |
Keep the order fixed: check the error code first → then the session route → finally whether every tool fails.
DSH plugin: Type 1: the review request carries no sessionId
OpenCode Go requires x-opencode-session on every inference request, but the review request passes no sessionId, so the provider never adds the header and every review gets a 400 before it reaches the model.
- Read the error:
auto-review: reviewer ended with error INVALID_REQUEST: 400: {"type":"MissingSessionID", ...}. - Confirm the session model uses the
opencode-goroute (the community case wasopencode-go/deepseek-v4.1-flash). - Verify the dependency directly: POST to the same endpoint without
x-opencode-sessionreturns 400, and with any string returns 200. - Understand the chain: pi-ai's
withOpenCodeSessionHeader()injects the header only whenoptions.sessionIdexists, andclassifyRisk()assemblesGenerateOptionswithout that field. - Compare other auxiliary calls: the main loop, compaction (
compaction-basic), and session titles all passsessionId; only auto-review misses it. - Current state: the community checked master and
classifyRiskstill has not added the line, so this is not "upgrade and it goes away". - Workaround: switch the permission preset to a non-Auto one such as
never, or pin a fixedheaders.x-opencode-sessionin that route's config — at the cost of losing per-session routing.
DeepSeek Harness: Type 2: hardcoded temperature: 0
The review request deep-freezes temperature: 0 and hands it to ctx.llm.stream(); a model that only accepts 1 returns 400, and the failed review cancels every tool call before it runs.
- Read the error:
auto-review: reviewer ended with error INVALID_REQUEST: 400: {"message":"invalid temperature: only 1 is allowed for this model", ...}. - Confirm the session model belongs to the "only accepts
temperature=1" class (the community example was a Kimi Code model). - Understand the location:
auto-review/src/index.tshardcodestemperature: 0when building options, and the serialization layer forwards any field that exists; OpenAI-compatible routes have no capability gate. - Install the community plugin:
dsh plugin --profile <profile> add github:argszero/dsh-auto-review-temperature-guard#v0.1.0. - Add a mount entry in the profile named
@argszero/dsh-auto-review-temperature-guard; with no parameter it omitstemperatureand lets the route default apply, and with one it sends your value. - Verify: review requests no longer carry 0 and tool calls resume; non-review requests must pass through untouched, which is one of the plugin's own assertions.
The plugin identifies review requests by whether system starts with REVIEW_POLICY; ordinary turns have system undefined, so chat requests are not touched. Its boundary is explicit: if it cannot match, it silently does nothing, which is fail-open.
DSH Plugin: Type 3: technical failures treated as denial
On transport errors, timeouts, quota limits, or malformed output the reviewer uniformly returns kind: 'deny', and this path runs before await next(), so human approval never gets a chance to step in.
- Distinguish by wording:
Auto review rejected tool …is a real judgment followed by human approval;Auto review of tool … failedis a reviewer failure that ends the call immediately. - Count with that prefix; the community verified it does not mix with the rejection wording.
- Read the code: the
failed()comment says "as a separate error rather than a denial", yet the returnedkindis still'deny'; the difference survives only asAUTO_REVIEW_FAILED_ERROR_NAMEinsideinfo. - A third path already exists next door:
askUser()(kind: 'ask'). The community suggests downgrading technical failures to "needs human confirmation", made configurable withaskas the default. - Two aggravating factors:
parseDecisionrunsJSON.parseon the raw text with no fence stripping, no balanced-bracket extraction, and no one-shot format-repair retry; and whenschemas.length !== 1it throws a missing-schema error, which MCP reconnects and their tool-set drift can trigger. - Workaround: switch to
danger-full-accessorworkspace-writeto bypass the reviewer, or move to a route with no format violations — but this is mitigation only; the safety layer's reliability should not depend on which chat model you picked.
DeepSeek Harness: Four steps to confirm "the reviewer is broken" rather than "the policy is too strict"
Start with one control experiment: same tool, same command, with Auto switched off. If it passes without Auto, the block came from the review stage, not the command.
- Run
/permissionin the session and switch toneverorworkspace-write. - Rerun the command that was just denied. If it passes, note the conclusion.
- Switch back to
autoand run a no-argument read-only tool, such as listing files or agents. - If the no-argument call is also denied, declare it a reviewer fault; only denials of commands with write or network arguments might be genuine risk judgments.
The value of these four steps is elimination: no source reading, no packet capture, and one pass separates "policy problem" from "reviewer problem".
DSH plugin: Why all three point at the same design problem
The reviewer reuses the session's own route and the package has no reviewer-model option, so the safety gate's reliability is tied to the chat model.
- Review requests take provider and model from the latest
request/header, so switching models means switching reviewers. - That is why "it works after changing models" is not a coincidence but structural.
- The long-term shape the community suggests is configuration: reviewer provider/model, maximum retries, and a per-review timeout.
- Until then, the fastest recovery for any of the three is switching the permission preset, rescuing the session, and only then investigating.
Cautions
Read the error prefix before changing any permission setting.
- Do not read "every tool is denied" as "the model sees risk". When no-argument read-only calls are denied, suspect the reviewer first.
- Do not treat
Auto review of tool … failedas a model rejection. The prefixes differ and so does the handling. - Pin the version when installing the plugin. The
latesttag ofexperimental-auto-reviewonce pointed at an older build, and not pinning may reproduce other problems. - The plugin is mitigation, not a fix. The hardcoded 0 and the missing line are still in the source.
- Record the error code and the session route. Together they are the entire information needed to tell the three faults apart; missing either leaves you guessing.
If your error contains neither MissingSessionID nor invalid temperature, it is most likely type 3 — search the session log for AUTO_REVIEW_FAILED to confirm.See DeepSeek Harness aborts tool calls before dispatch for the related case.
FAQ
Auto review in DeepSeek Harness fails with MissingSessionID because the review request carries no sessionId. pi-ai's opencode-go provider adds the x-opencode-session header only when it receives options.sessionId, and classifyRisk() does not pass one when assembling GenerateOptions. The community checked master and the line is still missing, so switch to a non-Auto preset or pin the header on the route.
No, the model is not incompatible; the DeepSeek Harness reviewer hardcodes temperature: 0. auto-review/src/index.ts sets 0 and deep-freezes it when building the request, and models that only accept 1 return 400. The community plugin dsh-auto-review-temperature-guard can omit it or set a specific value.
DeepSeek Harness denies the call outright because the kind returned by failed() is still 'deny'. The comment says it should be treated as a separate error rather than a denial, but the permission layer only looks at kind; askUser() (kind: 'ask') already exists next door as a fallback to human confirmation, and it is not used.
Two message prefixes separate the cases in DeepSeek Harness: Auto review rejected tool … is a real rejection and goes to human approval, while Auto review of tool … failed means the reviewer itself failed and the call ends immediately. When the latter appears on a no-argument read-only call, risk is almost certainly not involved.
The same Auto preset can behave differently after a model switch because DeepSeek Harness reuses the session's own request/header route for the reviewer, and the package has no reviewer-model override. The safety layer's reliability is tied to the chat model: move to a model that accepts temperature 1 and has a session route, and both kinds of 400 disappear.
Related Terms
- auto-review
- auto-review is an experimental DeepSeek Harness permission preset that calls a model once to assess risk before a tool executes. A failed assessment denies the call, and the current implementation also denies when the reviewer itself fails, making it fail-closed.— https://github.com/deepseek-ai/deepseek-harness/discussions/10954
- classifyRisk
- classifyRisk is the function in auto-review that assembles the review request, taking provider and model from the session's latest request/header. The community check found it passes neither a sessionId nor anything but a fixed temperature of 0.— https://github.com/deepseek-ai/deepseek-harness/discussions/10643
- x-opencode-session
- x-opencode-session is the request header OpenCode Go requires for per-session routing attribution. pi-ai's provider adds it only when the caller supplies a sessionId, and a missing header returns 400 MissingSessionID.— https://github.com/deepseek-ai/deepseek-harness/discussions/10643
- fail-closed
- fail-closed is a security-gate policy of denying rather than allowing when the gate itself malfunctions. auto-review is fail-closed, but it maps 'reviewer unavailable' and 'reviewer judged this dangerous' to the same deny result, leaving users unable to tell them apart.— https://github.com/deepseek-ai/deepseek-harness/discussions/10954
Sources
- #10643 — Auto review fails every tool call when the session model routes through OpenCode Go· deepseek-ai (GitHub Discussions)
- #11157 — master still has not added the sessionId line· deepseek-ai (GitHub Discussions)
- #11196 — auto-review hardcodes temperature: 0, so every tool is denied on models that only accept 1· deepseek-ai (GitHub Discussions)
- #10954 — Reviewer technical failures return a terminal deny (no human fallback)· deepseek-ai (GitHub Discussions)