DeepSeek Harness: session 400 when every tool is deferred

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek Harnessdefer_loadingINVALID_REQUESTtool callingsession recovery
Old sessions return INVALID_REQUEST on every turn while new ones work. Restore a loadable tool, patch the projector, or migrate the session.

An old session worked yesterday, but today every message returns INVALID_REQUEST while a new session with the same model and tools is fine. That split rules out a broken model or key: restoring the history changes the tool declarations, because DeepSeek Harness (DSH) marks every recorded addition as defer_loading and the llm-deepseek adapter keeps only tools declared in the current request. The two rules together can leave nothing loadable.

Three signals that point at the DeepSeek Harness tool list

A failure that begins only after restore, carries INVALID_REQUEST, and survives a one-word message is a tool-declaration problem until proven otherwise. Confirm it with these checks:

  1. Send a trivial message — type "hello" into the restored session and send it; expected: the same 400, which shows the failure is independent of your task.
  2. Compare the tools array — open the debug log and measure the tools array before and after restoring the session; expected: the length collapses to zero, or to a single placeholder entry.
  3. Borrow another provider — switch the session to an adapter that does not use addition-only and send one message; expected: a success narrows the fault to the declaration layer.

According to the reproduction in the discussion, projectToolUpdates labels every tool-addition with deferLoading: true, while models.ts:13 in llm-deepseek sets toolUpdate to 'addition-only'; any declaration outside the current options.tools is dropped (#10163). For context management around tools, see context plugins.

Why restoring a session empties the DSH tool list

Both rules look only at the present request, so a restored history can end up with declarations but no tool the model may actually load. Walk the chain:

  • Replay the stored events — restoration hands the session log to projectToolUpdates; every historical tool-addition is replayed.
  • Watch the defer flag — each replayed addition receives deferLoading: true, so no tool remains immediately loadable.
  • Apply the adapter filter — addition-only deletes anything missing from options.tools, and the surviving array can be empty or hold only a placeholder.
  • Send the request — the model receives deferred declarations without a loadable entry, and the server rejects it rather than guessing a schema.

Nothing in that chain depends on how long the conversation is, which is why trimming text or retrying does not help.

Three DeepSeek Harness fixes for all-deferred tools

Bottom line: install the guard plugin if you cannot touch the core, patch the projection if you maintain a fork, and migrate the session if you just need to keep working.

Remediation A: add the all-deferred-tools-guard plugin

This is the fastest repair that leaves the core untouched: the plugin notices an all-deferred request in llm/stream and injects one loadable placeholder so the request becomes legal again. Install and tune it in this order:

  1. Install the plugin — add @argszero/dsh-all-deferred-tools-guard from the plugin market; expected: its status reads active.
  2. Set the threshold — choose "all tools deferred" rather than "any tool deferred"; expected: healthy sessions are never patched.
  3. Restart and send — relaunch DSH, open the affected session, and send a message; expected: no 400.
  4. Confirm the injection — search the log for a guard record; expected: exactly one injection, proving the plugin acted rather than the model succeeding by luck.
  5. Check latency — watch the first turn on a large tool set; expected: one extra declaration only, with no visible slowdown.

Remediation B: keep one tool loadable in the projection

The root cause sits in projectToolUpdates: either leave one tool unflagged, or stop discarding historical declarations. Both changes are small, but they trade different costs:

  1. Count the additions — open packages/llm/llm/src/content.ts and total the tools added in the projection loop; expected: you know how many would be deferred.
  2. Skip one flag — when every addition would be deferred, leave the first one unflagged; expected: at least one loadable declaration survives.
  3. Or merge instead — change toolUpdate at models.ts:13 from addition-only to a mode that keeps historical declarations; expected: older tools stay declared, at the cost of a larger body.
  4. Rebuild and replay — start DSH and restore a session with several tool rounds; expected: messages stop failing.
  5. Pin the cases — add tests for all-deferred, partly deferred, and no deferred tools; expected: each behaviour is locked in.
ChangeBenefitCost
Keep one loadable toolTiny patch, no protocol changeThe model may load a tool it does not need
Merge declarationsMatches intuition and fixes similar casesLarger request body and higher token cost on long sessions

Remediation C: migrate the conversation to a fresh session

When neither a plugin nor a code change is acceptable, move the conclusions out and leave the poisoned projection behind; the price is losing the tool-call chain.

  1. Harvest the outcome — copy the last few rounds of conclusions into a clipboard or export them as Markdown; expected: the decisions and file paths survive outside the session.
  2. Start clean — create a new session and paste that background in; expected: the request is assembled from a fresh tool list.
  3. Rebuild the old one if it must stay — open it under another provider, complete one tool call, then switch back; expected: the list is recomputed and the session becomes usable.
  4. Archive only after proof — send two tool-calling rounds in the new session first; expected: both complete before you retire the old transcript.

Verify DSH fixes and know the limits

A single successful turn is weak evidence; the fix has to survive repeated restores and provider changes. Run this check:

  1. Replay a long session — restore one with more than ten tool rounds and send three messages; expected: none returns 400.
  2. Repeat in a new session — perform the same tool calls there; expected: the fix introduced no missing tools.
  3. Round-trip the provider — switch away and back once; expected: the tool list rebuilds correctly.
  4. Measure the body — inspect the request size after the fix; expected: declarations stay proportionate and latency does not regress.
  5. Check a forked session — open a child created from an affected transcript; expected: it does not silently inherit the same tool events.

Avoid these traps:

  • Retrying is pointless: an all-deferred list is deterministic, so resending produces the same rejection.
  • A placeholder is not a schema: the guard makes the request legal but does not restore the deferred definitions.
  • addition-only is deliberate, so confirm historical tool cleanup still works before switching to a merge policy.
  • No official fix version is named; 0.2.1-rc.2 still reproduces, so check release notes and do not assume an old DSH plugin behavior is fixed.

DeepSeek Harness all-deferred tools FAQ

Q: Why does a DeepSeek Harness old session fail every turn while a new one works? A: DeepSeek Harness fails the old session because restore marks every tool addition defer_loading, while the adapter accepts only tools declared in the current request. When the last loadable tool is dropped, the request carries no loadable declaration and the server answers 400; a new session recomputes the list.

Q: What is defer_loading in DeepSeek Harness, and how can the tool list become empty? A: DeepSeek Harness defer_loading lets the model see a tool name and summary first and load the full schema when needed, which requires at least one non-deferred tool. Restoring marks every addition deferred, the addition-only rule removes tools not declared for this request, and the loadable set can end up empty.

Q: Can I rescue a poisoned DeepSeek Harness session without changing source code? A: A poisoned DeepSeek Harness session can be rescued with the community all-deferred-tools-guard plugin, which detects an all-deferred request at the llm/stream layer and adds a same-name placeholder. You can also export the key messages into a new session or temporarily switch to a provider without addition-only.

Q: How do I tell a DeepSeek Harness deferred-tool 400 from a context-window error? A: A DeepSeek Harness all-deferred failure is INVALID_REQUEST, reproduces on a one-word message, and disappears in a new session, while a window error mentions tokens or the model window. Compare the tools array length before and after restore; zero or a single placeholder confirms the tool list.

FAQ

Why does a DeepSeek Harness old session fail every turn while a new one works?

DeepSeek Harness fails the old session because restore marks every tool addition defer_loading, while the adapter accepts only tools declared in the current request. When the last loadable tool is dropped, the request carries no loadable declaration and the server answers 400; a new session recomputes the list.

What is defer_loading in DeepSeek Harness, and how can the tool list become empty?

DeepSeek Harness defer_loading lets the model see a tool name and summary first and load the full schema when needed, which requires at least one non-deferred tool. Restoring marks every addition deferred, the addition-only rule removes tools not declared for this request, and the loadable set can end up empty.

Can I rescue a poisoned DeepSeek Harness session without changing source code?

A poisoned DeepSeek Harness session can be rescued with the community all-deferred-tools-guard plugin, which detects an all-deferred request at the llm/stream layer and adds a same-name placeholder. You can also export the key messages into a new session or temporarily switch to a provider without addition-only.

How do I tell a DeepSeek Harness deferred-tool 400 from a context-window error?

A DeepSeek Harness all-deferred failure is INVALID_REQUEST, reproduces on a one-word message, and disappears in a new session, while a window error mentions tokens or the model window. Compare the tools array length before and after restore; zero or a single placeholder confirms the tool list.

Related Terms

defer_loading
defer_loading is a DeepSeek Harness tool-declaration optimization that shows the model a name and summary first and loads the full parameter schema before a call. It requires at least one loadable declaration in the request, otherwise the tool set cannot be resolved.— https://github.com/deepseek-ai/deepseek-harness/discussions/10163
addition-only
addition-only is the tool-update policy of the llm-deepseek adapter. Only tools declared in the current request survive; historical tools outside that list are removed. Combined with all-deferred projection, it can empty the loadable set.— https://github.com/deepseek-ai/deepseek-harness/discussions/10163
projectToolUpdates
projectToolUpdates is the function in the llm package's content module that projects tool changes recorded in a session into a model request. It stamps each added tool with deferLoading, which is where this failure begins.— https://github.com/deepseek-ai/deepseek-harness/discussions/10163

Sources