DeepSeek Harness 400 on switch back: missing thinking blocks

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek Harnessdeepseek-officialthinking block400model switchingserialization
Mixed-provider history sent to deepseek-official returns content thinking must be passed back on every turn, while a fresh session stays healthy.

The same conversation ran for several turns on another model. After switching back to deepseek-official, every message returns 400 with content[].thinking ... must be passed back, while a new session is completely normal. Part of the history contains tool calls without the thinking block that produced them; nothing is wrong with the model or key, DeepSeek Harness (DSH) will not invent one, and the server refuses the whole transcript.

Confirm the DeepSeek Harness history is missing blocks

A 400 that appears only on a restored history, names thinking or passed back, and survives a one-word message is a missing-block problem; a failure in a new session points at configuration instead. Check in this order:

  1. Send a trivial message — type "hello" in the affected session; expected: still 400, which removes your prompt from suspicion.
  2. Try a tolerant provider — switch to one that does not require thinking playback and send the same message; expected: a success shows the requirement differs per adapter.
  3. Inspect the last tool round — in the debug log, find the final assistant message carrying tool_use and check whether the same turn contains thinking or reasoning; expected: the field is absent on the failing rounds.

According to the source review in the discussion, llm-deepseek/src/serialize.ts:24-37 emits thinking only when the stored message already has a reasoning block, and the local precheck at :131-146 validates tool pairing without checking thinking, so the defect reaches the server (#10077). For normal model switching, see model provider configuration.

How DSH mixed routing loses reasoning

A round generated with thinking off is stored without reasoning; the serializer respects that absence, while the server treats tool_use without thinking as invalid. Follow the sequence:

  • Run early rounds on provider A — a provider without thinking, or with it disabled, returns only tool_use, so the stored message has no reasoning.
  • Switch to deepseek-official — the history is carried over unchanged and the missing field travels with it.
  • Serialize the request — the adapter sees no reasoning and emits no thinking instead of synthesizing one.
  • Let the server validate — it finds a tool call without its thinking and rejects the entire request.
  • Send another message — the same history is attached again, so the failure repeats indefinitely.

Compaction is not required for this, but it removes reasoning more readily, so it makes an existing problem worse.

Three DeepSeek Harness fixes for missing thinking blocks

Bottom line: keep the transcript with a guard plugin, patch the serializer if you can ship code, and migrate the context when the old session has little value.

Remediation 1: run the community thinking-roundtrip-guard plugin

This repairs the protocol without touching the core and offers the finest control, which makes it suitable for production triage. Install it and move through the modes deliberately:

  1. Install the plugin — add @argszero/dsh-thinking-roundtrip-guard and confirm it is active; expected: the guard appears in the plugin list.
  2. Start in observe mode — replay the affected session and read the log; expected: the rounds missing thinking are listed precisely.
  3. Switch to synthesize — let the plugin insert a minimal thinking block for those rounds; expected: the next message returns 200.
  4. Fall back to degrade if behaviour changes — use it when the model reacts oddly to synthetic content; expected: the round is sent as an ordinary message without playback.
  5. Settle on one mode — observe for several turns before choosing a permanent setting; expected: no new 400 and acceptable answer quality.
ModeWhat it changesBest for
observeRecords onlyMeasuring the scope
synthesizeAdds a minimal thinking blockCompatibility with least disruption
degradeSends the round without thinking playbackCases where synthesis is rejected

Remediation 2: synthesize a block in the serializer

The durable fix belongs in serialize.ts: when an assistant turn contains tool_use but no reasoning, emit a minimal thinking block instead of skipping it. This path suits a fork or an upstream patch:

  1. Detect the case — before writing an assistant message, check whether the turn includes tool_use; expected: tool rounds are identified.
  2. Insert a placeholder — if tool_use exists without reasoning, add a minimal block with empty or one-line text; expected: every tool round carries thinking.
  3. Place it first — order the block before tool_use so the sequence matches the server's expectation; expected: the request passes validation.
  4. Test three histories — one with reasoning, one without, one mixed; expected: all three serialize correctly.
  5. Replay a real session — rebuild and open a conversation that crossed providers; expected: messages succeed without the plugin.

Synthetic content persists and travels in later requests, so keep it short. If the server validates a thinking signature rather than presence, fall back to Remediation 1 or 3. Upgrades can overwrite the adapter change.

Remediation 3: start a fresh session with exported context

When the transcript has little value, moving the conclusions is cheaper than repairing history, at the cost of the tool-call chain.

  1. Export the essentials — copy the last few conclusions, file paths and open tasks from the old session; expected: the working state is preserved in text.
  2. Open a new session — paste that background and confirm the selected model is deepseek-official; expected: no inherited tool rounds.
  3. Probe before working — send one plain-text message, then one that calls a tool; expected: both return 200.
  4. Archive the old session — keep it out of the way so it is not reopened by accident; expected: the failing transcript stops being part of daily work.

Verify DSH fixes and avoid regressions

The repair must hold across a provider round trip, not just on the first retry. Confirm:

  1. Replay three messages — use a session that started on A and moved to deepseek-official; expected: none returns 400.
  2. Inspect the body — check that every round with tool_use also carries thinking; expected: no gaps remain.
  3. Switch away and back — repeat the original model change; expected: the history is not damaged a second time.
  4. Control with a new session — send a normal request there; expected: no regression from the fix.

Avoid these traps:

  • Do not resend after a 400, because the history is unchanged and the result will be identical.
  • Check compaction behaviour before relying on it; if reasoning is dropped there, a repaired session can regress.
  • Synthetic thinking restores protocol compatibility only, not the model's earlier line of thought.
  • No official fix version is named; 0.2.1-rc.2 still reproduces, so check release notes and do not read an old DSH plugin behavior as current.

DeepSeek Harness thinking round-trip FAQ

Q: Why does DeepSeek Harness return 400 on every message after switching back to deepseek-official? A: DeepSeek Harness returns 400 because some earlier rounds were produced with thinking disabled, so those stored messages have no thinking block. deepseek-official requires tool-calling rounds to carry thinking back, while the serializer only emits it when reasoning exists; a new session has no such history.

Q: Did compaction delete the thinking blocks in DeepSeek Harness, or were they never there? A: DeepSeek Harness thinking blocks are often missing before compaction, because mixed routing stores a round from a provider without thinking as a message with no reasoning. Returning to deepseek-official does not synthesize one; compaction makes the loss more likely but is not the only source.

Q: How can I repair an old DeepSeek Harness session instead of abandoning it? A: An old DeepSeek Harness session can be repaired with the community thinking-roundtrip-guard plugin, using synthesize or degrade mode at the llm/stream layer. You can also patch serialize.ts to insert a minimal block before emitting tool_use; back up the session first and validate with a short message.

Q: Is the DeepSeek Harness 400 raised locally or by the server, and why was it missed? A: The DeepSeek Harness 400 is raised by the server. The local precheck verifies tool_use and tool_result pairing but not thinking completeness, so a request can pass locally and still be rejected upstream; catch it by checking thinking before sending whenever the provider requires playback.

FAQ

Why does DeepSeek Harness return 400 on every message after switching back to deepseek-official?

DeepSeek Harness returns 400 because some earlier rounds were produced with thinking disabled, so those stored messages have no thinking block. deepseek-official requires tool-calling rounds to carry thinking back, while the serializer only emits it when reasoning exists; a new session has no such history.

Did compaction delete the thinking blocks in DeepSeek Harness, or were they never there?

DeepSeek Harness thinking blocks are often missing before compaction, because mixed routing stores a round from a provider without thinking as a message with no reasoning. Returning to deepseek-official does not synthesize one; compaction makes the loss more likely but is not the only source.

How can I repair an old DeepSeek Harness session instead of abandoning it?

An old DeepSeek Harness session can be repaired with the community thinking-roundtrip-guard plugin, using synthesize or degrade mode at the llm/stream layer. You can also patch serialize.ts to insert a minimal block before emitting tool_use; back up the session first and validate with a short message.

Is the DeepSeek Harness 400 raised locally or by the server, and why was it missed?

The DeepSeek Harness 400 is raised by the server. The local precheck verifies tool_use and tool_result pairing but not thinking completeness, so a request can pass locally and still be rejected upstream; catch it by checking thinking before sending whenever the provider requires playback.

Related Terms

thinking block
A thinking block is reasoning content returned by a DeepSeek model. In tool-calling rounds it must be sent back with the history; the server uses it to preserve reasoning continuity and rejects requests where it is missing.— https://github.com/deepseek-ai/deepseek-harness/discussions/10077
serialize.ts
serialize.ts is the llm-deepseek module that converts stored messages into a request body. It decides which reasoning, tool_use and tool_result parts are sent and only emits thinking when the stored message already has reasoning.— https://github.com/deepseek-ai/deepseek-harness/discussions/10077
mixed routing
mixed routing is running one conversation across different providers or models. Their requirements for replaying thinking differ, so fields can be missing after a switch even though every individual round was valid when generated.— https://github.com/deepseek-ai/deepseek-harness/discussions/10077

Sources