DeepSeek Harness 400: tool call thought_signature missing
The DeepSeek Harness signature reaches the client on the first response; the adapter just never stores it, so there is nothing to send back. This 400 has nothing to do with keys, model names or quota: it only appears on the second turn of a conversation that used a tool, and the first turn always succeeds. The data-flow walkthrough below narrows it to one file, then gives three ways to handle it (source).
What the DeepSeek Harness 400 says and when it fires
Bottom line: if a turn contained a tool call, the next turn fails; disable tools or change the model and the same session recovers immediately.
Function call is missing a thought_signature in functionCall parts. This is required
for tools to work correctly, and missing thought_signature may lead to degraded model
performance. Additional data, function call `default_api:get_weather`, position 2.
- Configure a model against Google's OpenAI-compatible endpoint (
https://generativelanguage.googleapis.com/v1beta/openai/). Expected: ordinary chat works. - Ask something that has to call a tool, such as a weather lookup. Expected: the call succeeds and the tool result comes back.
- Let the model answer from that result. Expected: HTTP 400, naming a specific
function calland its position. - Repeat the same prompt with tools disabled. Expected: a normal reply, which proves only the replay path is broken.
- If step 3 returns
400 status code (no body)instead of the text above, read the limits section first — that is a different error-body problem. - Note the wording: the signature is missing, not invalid, so the key and project permissions are not suspects.
Where the signature is lost: four stages
Bottom line: the chain is "response carries the signature → the block builder does not read it → persistence never sees it → replay sends no field", and only the first stage is Google's behaviour.
- Capture one raw response and look inside
tool_callsforextra_content.google.thought_signature. Expected: a long base64-like string, which proves the signature did arrive. - Check whether that value lands on the tool-call block. Community review of the published package found neither
extra_contentnorthought_signaturein theopenai-completionsblock builder, so the field is discarded on first contact (per the discussion). - Check persistence and replay on the harness side. The discussion confirms
thoughtSignatureis already defined on the tool-call block, saved in the message frame, and cleared when you switch models. Nothing drops it a second time. - Conclusion: there is exactly one place to fix — the adapter's block builder and replay conversion. Do not look in the agent loop or the UI. A different 400 about tool parameter schemas is covered in parameter schema rejection.
Fix 1: patch the DeepSeek Harness adapter to capture and replay
Bottom line: the shortest path is two edits — store the signature on the block while building it, then write it back into extra_content when converting history. No DSH plugin setting changes this; the field is dropped inside the adapter itself.
- Enable a patch mechanism in the project (pnpm's
patchedDependencies, for example) and pin@earendil-works/pi-aito the version the patch matches. Expected: the install log records a patch for that package. - In
dist/api/openai-completions.js, after the argument string is read, add the capture:
const extraThoughtSignature = toolCall.extra_content?.google?.thought_signature;
if (extraThoughtSignature && !block.thoughtSignature) {
block.thoughtSignature = extraThoughtSignature;
}
Expected: the block carries thoughtSignature with exactly the value from the response.
- In the replay conversion, write the signature back onto the tool-call entry. Expected:
tool_calls[]in the next request containsextra_content.googleagain. - Re-run the reproduction from the first section. Expected: no 400 on turn two, and the model uses the tool result.
- Deliberately corrupt the signature and retry. Expected: the error changes from
400 status code (no body)to the explicitFunction call is missing a thought_signature— which also needs the error-body unwrapping patch, otherwise you have fixed data loss but not readability. - Record which package version the patch targets. Expected: any dependency resolution change is visible against that record.
- Run
git apply --checkbefore committing the patch and lockfile together. Expected: no rejected hunks. - If you only want to prove the chain without editing code, log whether the block's
thoughtSignatureis populated at replay time. Expected: present on turn one, still empty on turn two, which pins the loss to the capture side.
Fix 2: upgrade the DeepSeek Harness pi-ai package and re-check offsets
Bottom line: upgrading is the long-term fix, but the patch is line-number based — confirm the touched regions still sit where it expects before applying.
- Check the resolved pi-ai version. Expected: the repository declares a range such as
^1.1.0, while the installed version may already be a 1.3.0-class release on npm. - Search the target version for
ensureToolCallBlockandconvertMessages. Expected: both still exist and their structure has not changed materially. - Try the patch against the target tarball with
git apply --check. Expected: clean application or an explicit offset failure; do not force it through with--fuzz. - On an offset failure, rebuild the hunks against the new line numbers, moving context only and leaving the logic alone. Expected:
--checkpasses again. - After upgrading, run a real two-turn tool flow. Expected: the signature round-trips and block indexes and tool ids stay aligned.
- Commit the patch with the lockfile. Expected: nobody resolves a new version and silently loses the patch.
Fix 3: switch DeepSeek Harness to the native Gemini adapter
Bottom line: if you do not need the compatibility layer, the native adapter avoids the whole extra_content convention — at the cost of a different request and tool-schema mapping.
- Add a separate provider for Gemini in the model configuration, using the native protocol instead of
openai-completions. Expected: the model is listed on the settings page. - Create a new session on that provider and run plain chat, then a tool call. Expected: both turns go through.
- Inspect replay: tool-call blocks should carry a signature and turn two should not fail. Expected: the same behaviour as Fix 1.
- Do not resume old sessions on the new provider; copy the essential context into a fresh one. Expected: avoids the same 400 from blocks that never had a signature.
- Confirm the tool schema maps without loss under the native protocol, especially nested objects and enums. Expected: call arguments match what the tools expect.
- Keep the old provider entry and do not delete it yet. Expected: a one-step fallback if something breaks.
Verify the DeepSeek Harness fix and know the limits
Bottom line: acceptance is one session surviving two consecutive tool-calling turns, with a third turn still working.
- Turn one issues a tool call; confirm the response carries a signature.
- Turn two must not 400 and must reference the tool result correctly.
- Turn three issues another tool call; confirm the signature survives several round trips.
- Export the session and confirm the block shows signature metadata rather than an empty field.
- Repeat with a tool that has a different parameter shape. Signature behaviour does not depend on the tool name, so a failure here means the capture sits in the wrong place.
Limits and caveats
Bottom line: two failure modes share one symptom but not one cause, and every claim below is community-verified against the published package, not an official fix.
- When the error body is a single-element array
[{error:{...}}], the OpenAI SDK cannot readerrorand the whole turn collapses to400 status code (no body). That is a separate upstream problem; do not evaluate both with one patch. - The patch in the discussion targets the published tarball, not the source repository, so it has to be re-aligned after every upgrade.
- Clearing the signature on model switch is intended behaviour. Do not change that code to "preserve" it.
- Native Gemini and the OpenAI-compatible endpoint are not fully equivalent in tool-schema mapping; retest arguments after switching.
- The line numbers and field behaviour above come from community review of the published package; upstream has not confirmed a fix, so treat this as a temporary measure.
- The signature is opaque. Do not truncate, escape or length-check it — store and return it verbatim.
- The patch only applies to your own install. Teammates replay it from the lockfile, so an unchanged lockfile means no fix for them.
DeepSeek Harness FAQ
Bottom line: these four questions cover the checks that most often send people down the wrong path.
Q: The first turn works and only the second fails. Why not a network problem? A: A network fault would not depend on whether the history contains a tool call. The failure tracks the replay payload, not connectivity.
Q: Does the native adapter remove the need for the patch? A: For new sessions on that provider, yes in practice. It does not repair history already stored without a signature.
Q: I upgraded and the patch applied without error but the 400 is back. What now?
A: Check that the hunk landed in the block builder and not in adjacent, similar code. --check proves the text matched, not that the capture runs.
Q: Does this affect text-only Gemini sessions? A: No. Without a tool call there is no signature to replay, which is why plain chat is a reliable control.
FAQ
No — a DeepSeek Harness 400 on the second turn means the signature is missing, not that the key or quota failed. The first turn always succeeds, and a credential problem would break that too.
The DeepSeek Harness patch touches two places. Capture extra_content.google.thought_signature into the block's thoughtSignature while the tool-call block is built, and write it back to extra_content during replay conversion. The block field is already persisted and already cleared on model switch, so no new serialization logic is needed.
DeepSeek Harness declares ^1.1.0 while npm already ships 1.3.0-class versions. The patch was generated against 1.1.0 line numbers, so on a newer release it is either rejected or applied to the wrong region.
The DeepSeek Harness native Gemini adapter bypasses the OpenAI compatibility layer's handling of extra_content, but it also swaps the request and tool-schema mapping, so old tool-call blocks need revalidation. Use it for new sessions or sessions you can rebuild, not for resuming old ones in place.
Related Terms
- thought_signature
- An opaque signature Gemini attaches to tool-call responses and expects the client to send back with the same call on the next turn. If it is missing, Google rejects that turn with HTTP 400.— https://github.com/deepseek-ai/deepseek-harness/discussions/10210
- extra_content.google
- The field namespace in an OpenAI-compatible response that carries Google-private metadata. thought_signature lives inside it, and neither the official OpenAI SDK nor most compatibility adapters parse it on their own.— https://github.com/deepseek-ai/deepseek-harness/discussions/10210
- openai-completions adapter
- The pi-ai implementation for the OpenAI Chat Completions protocol. It turns streaming deltas into content blocks and converts stored blocks back into request messages; the field is dropped at this layer, so nothing downstream can recover it.— https://github.com/deepseek-ai/deepseek-harness/discussions/10210
Sources
- Google Gemini-Argon-4 tool-call replay drops thought_signature in openai-completions· deepseek-ai/deepseek-harness Discussions
- @deepseek-ai/dsh-llm-pi-ai README· deepseek-ai