DeepSeek Harness second-turn 400: redacted_thinking, Gemini

TroubleshootingPublished 2026-10-30Author: DeepSeek Plugin Market
DeepSeek HarnessDSH400redacted_thinkingGeminiUNSUPPORTED_CONTENTschema
DeepSeek Harness succeeds on the first step, then fails with 400 when a redacted_thinking block is replayed. Gemini schemas cause a sibling 400 on every turn.

The first step succeeds and the second blows up — and that order is itself the clue: the block does not fail when it is received, it fails when it is replayed. The first response carried Anthropic's redacted_thinking block, which was written into history; the second step has to translate history back into a request, the adapter does not recognize the block, and it throws UNSUPPORTED_CONTENT (#11139). A sibling timeline lives on Gemini: the tool schema is resent verbatim every turn, so one bad schema breaks the whole session (#10904).

DeepSeek Harness: Turn 1: the block arrives but has not yet entered a request

The first turn succeeds because the block is only received; at that point the code follows the normal chunked-read path, and nothing demands that it be translated outbound.

  1. Environment: @deepseek-ai/dsh-llm-deepseek 0.2.1-rc.3, with the endpoint behind a Bifrost Anthropic-compatible gateway.
  2. Step one: the agent calls an MCP tool and it returns successfully.
  3. The response contains a redacted_thinking block — a block type defined by the Anthropic protocol, whose data is a signed payload.
  4. The session still looks fine and the user notices nothing.
  5. But this step's response is already on disk, becoming the history to replay next turn.

DSH plugin: Turn 2: history replay translates it again

Replay must translate every historical block into a form DeepSeek Messages can represent; startBlock() handles only text, thinking, and tool_use, and throws on the default branch for everything else.

  1. Read the error: DeepSeek Messages does not support response block redacted_thinking, with code=UNSUPPORTED_CONTENT.
  2. In the installed package the throw sits at lib/index.js:1878, corresponding to the translation layer in packages/llm/llm-deepseek.
  3. The event stream shows assistant/attempt ending in error and turn/end carrying the same error.
  4. Consequence: every tool call after step two is unusable, and a multi-step agent loop breaks outright.
  5. Switching models (deepseek-v4-pro and deepseek-flash) does not help, and reasoningEffort: none does not either — the trigger is the block type, not the model or reasoning level.

DeepSeek Harness: Why "drop the block" is not a fix

Its data is a signed payload that later steps depend on returning verbatim; accepting but not recording it silently changes reasoning continuity, turning a loud failure into a silent one.

  1. The repository's protocol round-trip test already states the requirement: input { type: 'redacted_thinking', data: '...' }.
  2. The recorded form is { type: 'reasoning', thinkingSignature: '...', redacted: true }.
  3. The assertion requires the next request to still carry that payload.
  4. Three places in the dsh-llm-deepseek chain must change together: the translation layer throwing on unknown blocks, the ReplayBlock type lacking redacted and data fields, and the serializer writing reasoning only as thinking + signature.
  5. So the real fix is "accept it, record it, and emit it again at the original index", a persistence-structure change that needs version coordination.

Another boundary matters too: the throw happens inside the translate() generator, before any llm/stream listener. An outer wrapper only sees "an error", never the block; and the request side also needs the payload to have been persisted, or there is nothing to replay externally.

DSH Plugin: The sibling timeline: Gemini resends its schema every turn

A more extreme variant of "fails on the second turn" is not the second turn but every turn: the full tool list is sent each time, so one bad schema fails the whole session.

  1. Symptom: after switching a session to Gemini, every message returns 400 INVALID_ARGUMENT, regardless of content.
  2. Comparison: no tools 200, flat schema 200, oneOf 200; $ref, $defs, and additionalProperties all 400.
  3. Error detail: Invalid JSON payload received. Unknown name "$defs" at 'tools[0].function_declarations[0].parameters', while the UI shows only a generic Request contains an invalid argument.
  4. Cause: DSH forwards the tool schema verbatim, and Gemini accepts only an OpenAPI 3.0 subset.
Schema shapeResult
No tools / flat schema / oneOf200
Contains $ref / $defs400
Contains additionalProperties400
  1. Timeline difference: redacted_thinking is "something left by last turn blows up next turn", while a schema is "the same list rides every turn, so every turn blows up".

The community check also surfaced two structural facts: additionalProperties is mandatory for type: 'object' in the core schema language, so many built-in tools carry it naturally; and the MCP bridge mounts the server's inputSchema verbatim, leaving $schema and examples untouched.

DeepSeek Harness: Two temporary routes, cost-ordered

For redacted_thinking, move to the pi-ai anthropic-messages route; for Gemini schemas, attach the normalizer plugin to observe first, then wait for core to degrade at the adapter boundary.

  1. Switch routes (redacted case): use a pi-ai adapter route declared as anthropic with api: anthropic-messages, pointed at the same gateway. The in-repo round-trip test shows this path handles redacted blocks.
  2. The evidence for that is an in-tree test against a mock Anthropic server, not a measurement of your gateway; understand it as "an adapter that can handle the block", not "a verified fix".
  3. Attach the normalizer (Gemini case): @argszero/dsh-mcp-schema-normalizer 0.2.0, offering the inline-ref, prune-defs, and drop-additional-properties rules.
  4. Start with mode: observe, which only reports what it would change without touching requests; once the hit range is confirmed, switch to normalize.
  5. Order matters: mount it before the line that connects the MCP server.
  6. Know the boundary: it only affects mcp__<server>__<tool>, and built-in tools plus $schema and examples still return 400.

The community before/after numbers are vivid: $ref dropped from 745 to 0, $defs from 169 to 0, and additionalProperties from 1422 to 12; the remaining 12 belong to 8 non-MCP tools including ask_user_question, workflow, present, and todo_write.

DSH plugin: Fix direction: the decision belongs in serialization

The right fix for both classes sits at the adapter boundary: normalize once per protocol rather than asking tool authors to write multiple schemas.

  1. Redacted case: add the redacted flag and data to the durable replay envelope, emit it again at the original index during serialization, and update the tests that pin the current behavior.
  2. Gemini case: degrade before serializing a request for a route — inline $ref/$defs with a depth cap against recursion, remove unsupported keywords, and fall back to a permissive schema when needed.
  3. A more thorough approach is an allowlist posture: emit only the keyword set Gemini accepts, or deleting keys one by one will always run into the next one.
  4. The built-in declaration half can only be changed in core, out of a plugin's reach; the function_response half (another related report) is also in core.
  5. A cheap improvement along the way: surface the field-level reason already present in the response body (Unknown name "$defs") instead of only a generic message.

Cautions

Distinguish what the stream did from what the provider meant.

  1. Do not read "the first turn succeeded" as "no problem". The block is already on disk; the problem is merely delayed by one turn.
  2. Do not try to dodge it by lowering the reasoning level. reasoningEffort: none was measured to make no difference.
  3. Do not edit schemas inside a request listener. That request is deep-frozen; listeners can read but not replace.
  4. Do not treat the plugin as the core fix. It rewrites "what you tell the model", not "what the provider accepts".
  5. Include the field-level error when reporting. A generic message turns "protocol incompatibility" into a misleading "key or network problem".

One general lesson: both timelines share "something persisted is replayed next turn". Whenever the error position is identical every turn while the content has nothing to do with your input, look first at what block or declaration was most recently added to history.

If your 400 has neither a block type name nor Unknown name, it is a third class — go check the request body format and protocol endpoint.

The handling order also differs: redacted_thinking can only move routes or wait for core to change the persistence structure, while Gemini schemas can at least be partly suppressed with the plugin for the MCP half.See DeepSeek Harness 400: tool call thought_signature missing for the related case.

FAQ

The first tool call in DeepSeek Harness succeeds and the second reports UNSUPPORTED_CONTENT — why?

The second call fails with UNSUPPORTED_CONTENT because the first DeepSeek Harness response carried a redacted_thinking block, which was written into history. When the second step replays that history the adapter recognizes only text/thinking/tool_use and takes the default branch for anything else, reporting "DeepSeek Messages does not support response block redacted_thinking".

Can I just drop the redacted_thinking block in DeepSeek Harness?

No, dropping it is not safe in DeepSeek Harness, because its data is a signed payload that later steps must return verbatim; dropping it silently changes reasoning continuity server-side, turning a loud failure into a quiet one, which is worse.

Gemini in DeepSeek Harness returns 400 INVALID_ARGUMENT and switching to DeepSeek works — is it the tool schema?

Yes, the tool schema is the cause in DeepSeek Harness: switching back to DeepSeek works because Gemini's functionDeclarations accept only an OpenAPI 3.0 subset, and $ref, $defs, and additionalProperties return 400 immediately. The community measured 200 for no tools, flat schemas, and oneOf, and 400 for all three keywords.

Why does one bad tool schema make the whole DeepSeek Harness session unusable?

One bad tool schema poisons a DeepSeek Harness session because DSH resends the full tool list every turn, and if any one tool carries an unsupported keyword the request is rejected before inference. Failures therefore have nothing to do with your message and show up as 400 on every turn.

Can dsh-mcp-schema-normalizer fix Gemini's 400 in DeepSeek Harness?

Only partly: dsh-mcp-schema-normalizer rewrites MCP tool schemas in DeepSeek Harness by mcp__<server>__<tool> selector, and the community measured $ref/$defs/additionalProperties dropping from 745/169/1422 to 0/0/12; the remaining 12 come from built-in tools the plugin cannot reach, and $schema and examples are still rejected.

Related Terms

redacted_thinking
redacted_thinking is an Anthropic Messages content block carrying encrypted or redacted reasoning that clients must return verbatim without interpreting. Discarding it changes reasoning continuity in later requests.— https://github.com/deepseek-ai/deepseek-harness/discussions/11139
UNSUPPORTED_CONTENT
UNSUPPORTED_CONTENT is the error code the DeepSeek adapter returns on a response block it cannot represent. It is thrown during translation, before any stream listener, so external code cannot fix it by filtering responses.— https://github.com/deepseek-ai/deepseek-harness/discussions/11139
functionDeclarations
functionDeclarations is the structure in a Gemini generateContent request that declares callable functions, and its parameters accept only an OpenAPI 3.0 subset. Keys such as $ref, $defs, and additionalProperties return Cannot find field.— https://github.com/deepseek-ai/deepseek-harness/discussions/10904
inline-ref
inline-ref is the practice of expanding the subschema a local $ref points at directly inline, so endpoints like Gemini that only understand flat structures can accept a schema that originally used references.— https://github.com/deepseek-ai/deepseek-harness/discussions/10904

Sources