DeepSeek Harness 400: tool call thought_signature missing

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek HarnessGeminithought_signaturetool calling400 error
DeepSeek Harness returns 400 on the second Gemini tool turn because pi-ai drops thought_signature. Patch it, check offsets on upgrade, or switch adapters.

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.
  1. Configure a model against Google's OpenAI-compatible endpoint (https://generativelanguage.googleapis.com/v1beta/openai/). Expected: ordinary chat works.
  2. Ask something that has to call a tool, such as a weather lookup. Expected: the call succeeds and the tool result comes back.
  3. Let the model answer from that result. Expected: HTTP 400, naming a specific function call and its position.
  4. Repeat the same prompt with tools disabled. Expected: a normal reply, which proves only the replay path is broken.
  5. 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.
  6. 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.

  1. Capture one raw response and look inside tool_calls for extra_content.google.thought_signature. Expected: a long base64-like string, which proves the signature did arrive.
  2. Check whether that value lands on the tool-call block. Community review of the published package found neither extra_content nor thought_signature in the openai-completions block builder, so the field is discarded on first contact (per the discussion).
  3. Check persistence and replay on the harness side. The discussion confirms thoughtSignature is already defined on the tool-call block, saved in the message frame, and cleared when you switch models. Nothing drops it a second time.
  4. 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.

  1. Enable a patch mechanism in the project (pnpm's patchedDependencies, for example) and pin @earendil-works/pi-ai to the version the patch matches. Expected: the install log records a patch for that package.
  2. In dist/api/openai-completions.js, after the argument string is read, add the capture:
js
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.

  1. In the replay conversion, write the signature back onto the tool-call entry. Expected: tool_calls[] in the next request contains extra_content.google again.
  2. Re-run the reproduction from the first section. Expected: no 400 on turn two, and the model uses the tool result.
  3. Deliberately corrupt the signature and retry. Expected: the error changes from 400 status code (no body) to the explicit Function call is missing a thought_signature — which also needs the error-body unwrapping patch, otherwise you have fixed data loss but not readability.
  4. Record which package version the patch targets. Expected: any dependency resolution change is visible against that record.
  5. Run git apply --check before committing the patch and lockfile together. Expected: no rejected hunks.
  6. If you only want to prove the chain without editing code, log whether the block's thoughtSignature is 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.

  1. 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.
  2. Search the target version for ensureToolCallBlock and convertMessages. Expected: both still exist and their structure has not changed materially.
  3. 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.
  4. On an offset failure, rebuild the hunks against the new line numbers, moving context only and leaving the logic alone. Expected: --check passes again.
  5. After upgrading, run a real two-turn tool flow. Expected: the signature round-trips and block indexes and tool ids stay aligned.
  6. 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.

  1. 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.
  2. Create a new session on that provider and run plain chat, then a tool call. Expected: both turns go through.
  3. Inspect replay: tool-call blocks should carry a signature and turn two should not fail. Expected: the same behaviour as Fix 1.
  4. 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.
  5. Confirm the tool schema maps without loss under the native protocol, especially nested objects and enums. Expected: call arguments match what the tools expect.
  6. 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.

  1. Turn one issues a tool call; confirm the response carries a signature.
  2. Turn two must not 400 and must reference the tool result correctly.
  3. Turn three issues another tool call; confirm the signature survives several round trips.
  4. Export the session and confirm the block shows signature metadata rather than an empty field.
  5. 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.

  1. When the error body is a single-element array [{error:{...}}], the OpenAI SDK cannot read error and the whole turn collapses to 400 status code (no body). That is a separate upstream problem; do not evaluate both with one patch.
  2. The patch in the discussion targets the published tarball, not the source repository, so it has to be re-aligned after every upgrade.
  3. Clearing the signature on model switch is intended behaviour. Do not change that code to "preserve" it.
  4. Native Gemini and the OpenAI-compatible endpoint are not fully equivalent in tool-schema mapping; retest arguments after switching.
  5. 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.
  6. The signature is opaque. Do not truncate, escape or length-check it — store and return it verbatim.
  7. 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

Is the second-turn 400 caused by a bad key or an exhausted quota?

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.

How many places does the local patch touch?

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.

Why check patch offsets before upgrading the DeepSeek Harness pi-ai package?

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.

Can the native Gemini adapter continue my existing sessions?

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