openclaw - ✅(Solved) Fix [Bug] Google OpenAI-compat fallback 400 from unsupported store + missing thought_signature during replay [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#53658Fetched 2026-04-08 01:25:13
View on GitHub
Comments
1
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
commented ×1cross-referenced ×1

Error Message

Fallback/replay may fail with HTTP 400, then compaction safeguard triggers, and embedded run ends with an error payload.

PR fix notes

PR #66949: agents/google: round-trip Gemini 3 thoughtSignature on tool calls to unblock Gemini 3.x tool-calling

Description (problem / solution / changelog)

Summary

Impact: enables Gemini 3.x for openclaw tool-calling. Without this, any tool-calling turn on gemini-3-flash-preview (or Gemini 3.1 Pro Preview) via openclaw's native google-generative-ai transport softlocks on turn two — the model emits a tool call, openclaw executes it, and the follow-up request 400s because the signature was dropped in transit.

  • Problem: src/agents/google-transport-stream.ts parses thoughtSignature off thinking/text parts but not off tool-call parts. Google rejects any subsequent request whose functionCall parts are missing the signature that was attached on the way in: Function call is missing a thought_signature in functionCall parts. ... function call <namespace>:<tool>, position N.
  • Why it matters: Gemini 3 is unusable for tool calling through openclaw today. Agents on gemini-3-flash-preview show typing indicators and never reply. This blocks any MCP integration or tool-catalog plugin that guarantees a tool-call turn.
  • What changed: Capture part.thoughtSignature at the tool-call construction site in the SSE parser, and plumb it through GoogleTransportContentBlock.toolCall so the existing serializer (which already re-emits signatures byte-exact on same-provider-and-model replay) has data to emit.
  • What did NOT change (scope boundary): Only the native google-generative-ai transport. The OpenAI-compat path (#53658, #58235) has a structurally equivalent bug and is intentionally not bundled here — separate PR, separate concern. Cross-model replay still strips thoughtSignature via the existing transport-message-transform.ts logic (unchanged and verified).

Change Type

  • Bug fix

Scope

  • Integrations
  • API / contracts

Linked Issue/PR

  • Closes #63397 (Vertex AI Gemini — native path, exact match)
  • Related #34008 (Gemini 3 via Ollama Cloud — same missing-signature symptom, different transport)
  • Related #53658 (Google OpenAI-compat fallback — parallel bug on the compat path, intentionally out of scope here)
  • Related #58235 (Gemini 3.1 Pro Preview via OpenAI-compat API — same, compat path)
  • Context #5001, #841 (prior thought-signature-related reports)
  • This PR fixes a bug or regression

Root Cause

  • Root cause: Parser/serializer asymmetry. The serializer at src/agents/google-transport-stream.ts:355 already emits thoughtSignature when present on a tool-call block under same-provider-and-model replay. The parser at src/agents/google-transport-stream.ts:709 constructed the tool-call block without reading part.thoughtSignature, so the field was never set and the serializer had nothing to emit. The SSE chunk type already declared the field as a sibling to functionCall, so the wire model was correct — only the in-memory content block lost it.
  • Missing detection / guardrail: No regression test exercised round-tripping a thoughtSignature on a tool-call part. Thinking-block signatures were covered; tool-call signatures were not.
  • Contributing context (if known): Gemini 3 was the first generation where the signature became mandatory on tool-call replay. Gemini 2.5 and earlier tolerated missing signatures, so the gap hid until Gemini 3 preview models started rejecting those requests.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/agents/google-transport-stream.test.ts
  • Scenario the test should lock in: byte-exact thoughtSignature round-trip on tool-call parts across same-provider replay; stripped on cross-provider replay; parallel-call first-only preservation; absence stays absent.
  • Why this is the smallest reliable guardrail: the bug is a one-field data-flow failure between parser and serializer. A provider-level unit test is the tightest surface that reproduces it without needing a live model.
  • Existing test that already covers this (if any): none for tool-call signatures specifically. The existing "uses the guarded fetch transport and parses Gemini SSE output" case covered thinking-block signatures but asserted nothing about tool-call parts.

Five new tests added in this PR:

  1. Parser captures thoughtSignature on tool-call SSE parts
  2. Serializer emits it byte-exact on same-provider replay
  3. Serializer omits the field when the tool call carried no signature
  4. Serializer strips the field when replaying across providers (regression guard for cross-model leakage)
  5. Parallel tool calls: signature preserved only on the parts that had one (mirrors Google's documented emission rule — only the first functionCall in a parallel batch carries a signature)

User-visible / Behavior Changes

None on the happy path for non-Gemini-3 users — the field is absent end-to-end for models that don't emit it, and same-provider replay for Gemini 2.5 and below is byte-identical. Gemini 3.x users gain working tool-calling where previously every multi-turn tool conversation failed.

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No (same endpoints; adds one optional field to the request body that Google itself produced on the prior response)
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Linux x86_64, Node 22.22.1
  • Model/provider: gemini-3-flash-preview via google-generative-ai native transport
  • Integration/channel: any MCP server whose tool catalog guarantees a tool-call turn

Steps

  1. Configure an openclaw-driven agent on gemini-3-flash-preview with at least one tool plugin or MCP server.
  2. Send a query that routes through a tool.
  3. Observe the gateway emit two inference POSTs and zero outbound reply to the calling channel.

Expected

  • Agent replies. /v1beta/models/gemini-3-flash-preview:streamGenerateContent returns 200 on every turn of a multi-turn tool-calling conversation.

Actual (before fix)

  • Turn one succeeds (initial tool call). Turn two 400s with Function call is missing a thought_signature in functionCall parts. Please remove the thought signature from the previous Part.part at content[...] ... function call <namespace>:<tool>, position N. The agent never posts a reply.

Actual (after fix)

  • Both turns 200. Agent replies. Fetch-level 4xx/5xx capture stays empty across a multi-turn tool exchange.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets

Captured 400 response body before the fix (via a fetch-level preload):

Function call is missing a thought_signature in functionCall parts.
Please remove the thought signature from the previous Part.part at content[...].parts
function call <namespace>:<tool>, position 44

Test suite after the fix:

 Test Files  1 passed (1)
      Tests  10 passed (10)

Full src/agents/**/*.test.ts run: 365 files, 3799 passed, 4 skipped, 0 failed (144s).

End-to-end verified against a multi-tool-call conversation on gemini-3-flash-preview: every turn returned 200, the fetch-level error log stayed empty, and the agent completed the exchange normally.

Changed files

  • src/agents/google-transport-stream.test.ts (modified, +181/-0)
  • src/agents/google-transport-stream.ts (modified, +10/-1)
  • src/agents/openai-transport-stream.test.ts (modified, +247/-0)
  • src/agents/openai-transport-stream.ts (modified, +95/-0)
RAW_BUFFERClick to expand / collapse

Summary

Embedded fallback to Google OpenAI-compatible endpoint fails with 400 status code (no body) in OpenClaw agent runs.

I traced this to two payload-compatibility problems during fallback/replay:

  1. top-level store: false is sent to Google OpenAI-compat and rejected as unknown field.
  2. replayed tool/function call history can hit Gemini validation errors: missing thought_signature in functionCall parts.

Environment

  • OpenClaw: 2026.3.23-2
  • API mode: openai-completions
  • Provider: Google OpenAI-compatible endpoint (https://generativelanguage.googleapis.com/v1beta/openai/)
  • Model chain tested: local/qwen-3.5-35b-a3b -> google/gemini-3-flash-preview -> google/gemini-3.1-pro-preview
  • OS: Linux (Docker)

Reproduction

  1. Configure primary local model with Google fallback models.
  2. Force local timeout (e.g., low run timeout) to trigger fallback.
  3. Run embedded agent turn with tools enabled.
  4. Observe fallback fail with 400.

Evidence

  • Captured fallback request includes top-level keys like:
    • model
    • messages
    • tools
    • stream
    • max_completion_tokens
    • store (false)
  • Direct Google compat test confirms unknown store field returns 400 INVALID_ARGUMENT.
  • After stripping store, upstream can still return 400 on replay turns with:
    • Function call is missing a thought_signature in functionCall parts ... default_api:read ...

Expected Behavior

Fallback requests to Google OpenAI-compat should avoid unsupported fields and replay history in a way that does not violate thought_signature requirements.

Actual Behavior

Fallback/replay may fail with HTTP 400, then compaction safeguard triggers, and embedded run ends with an error payload.

Suggested Fix

  1. Respect provider/model compatibility for store in all OpenAI-compat paths (not only selective API branches).
  2. In fallback/replay for Gemini tool loops, preserve required thought_signature metadata OR avoid replaying function/tool-call parts that cannot be validated by target endpoint.
  3. Add regression tests for:
    • Google OpenAI-compat + store unsupported
    • fallback replay with tool-call history requiring thought_signature

Notes

This may be related to prior issues around store compatibility and Gemini thought_signature handling, but this report is for Google OpenAI-compat fallback path in embedded agent runs.

extent analysis

Fix Plan

To resolve the issue, we need to:

  • Remove the unsupported store field from the payload sent to the Google OpenAI-compatible endpoint.
  • Preserve the required thought_signature metadata in the function call parts during fallback/replay for Gemini tool loops.

Code Changes

Here are the concrete steps and code snippets:

Step 1: Remove unsupported store field

# Before sending the request to the Google OpenAI-compatible endpoint
if 'store' in payload:
    del payload['store']

Step 2: Preserve required thought_signature metadata

# When replaying function call parts during fallback
for function_call in payload['messages']:
    if 'function_call' in function_call and 'thought_signature' not in function_call['function_call']:
        # Either preserve the thought_signature or avoid replaying this part
        function_call['function_call']['thought_signature'] = 'default_signature'

Step 3: Add regression tests

# Test case 1: Google OpenAI-compat + store unsupported
def test_google_openai_compat_store_unsupported():
    # Send a request with the store field to the Google OpenAI-compatible endpoint
    response = send_request(payload={'store': False})
    assert response.status_code == 400

# Test case 2: Fallback replay with tool-call history requiring thought_signature
def test_fallback_replay_thought_signature():
    # Replay a function call part with a missing thought_signature
    payload = {'messages': [{'function_call': {'function': 'default_api:read'}}]}
    response = send_request(payload)
    assert response.status_code == 400

Verification

To verify that the fix worked, run the regression tests and check that the fallback requests to the Google OpenAI-compatible endpoint no longer return a 400 status code.

Extra Tips

  • Make sure to test the changes thoroughly to avoid introducing new issues.
  • Consider adding more test cases to cover different scenarios and edge cases.
  • Keep an eye on the Google OpenAI-compatible endpoint's documentation and updates to ensure compatibility.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING