hermes - 💡(How to fix) Fix [Bug]: Codex→DeepSeek fallback sends assistant turns without reasoning_content → HTTP 400 (require-side cross-provider failover) [2 pull requests]

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…

When the primary model is openai-codex / gpt-5.5 and a mid-conversation fallback switches to a require-side thinking provider (deepseek / deepseek-v4-pro, also Kimi / MiMo), the first request on the new provider is rejected with:

HTTP 400: The `reasoning_content` in the thinking mode must be passed back to the API.

The fallback never recovers — every retry replays the same un-padded history, so the turn dies even though a working fallback provider is configured.

Root Cause

agent/conversation_loop.py:

  1. L919–962api_messages is built once, before the retry loop. _copy_reasoning_content_for_api runs with the primary (Codex) active, so _needs_thinking_reasoning_pad() is False and assistant tool-call turns that carry no reasoning summary go out without reasoning_content (correct for Codex, which uses encrypted reasoning rather than reasoning_content).
  2. L1159 / 1395 / 1465_try_activate_fallback() switches provider → deepseek, then continues — without rebuilding api_messages.
  3. L1186_build_api_kwargs(api_messages) re-sends the stale, Codex-shaped messages to DeepSeek, which requires reasoning_content on every assistant turn → 400.

The pad logic itself is correct — it simply never re-runs after the provider switch.

Fix Action

Fixed

Code Example

HTTP 400: The `reasoning_content` in the thinking mode must be passed back to the API.
RAW_BUFFERClick to expand / collapse

Note: This issue (and its companion PR) were drafted by an AI assistant — Claude, Opus 4.7 — under human review, while diagnosing and fixing a real failure case on a personal Hermes deployment.

Summary

When the primary model is openai-codex / gpt-5.5 and a mid-conversation fallback switches to a require-side thinking provider (deepseek / deepseek-v4-pro, also Kimi / MiMo), the first request on the new provider is rejected with:

HTTP 400: The `reasoning_content` in the thinking mode must be passed back to the API.

The fallback never recovers — every retry replays the same un-padded history, so the turn dies even though a working fallback provider is configured.

Environment

  • Hermes main @ 9b5dae17a (reproduced on latest)
  • Primary: openai-codex / gpt-5.5 (https://chatgpt.com/backend-api/codex)
  • Fallback: deepseek / deepseek-v4-pro (https://api.deepseek.com/v1)
  • OpenAI SDK 2.24.0, Python 3.11

Root cause

agent/conversation_loop.py:

  1. L919–962api_messages is built once, before the retry loop. _copy_reasoning_content_for_api runs with the primary (Codex) active, so _needs_thinking_reasoning_pad() is False and assistant tool-call turns that carry no reasoning summary go out without reasoning_content (correct for Codex, which uses encrypted reasoning rather than reasoning_content).
  2. L1159 / 1395 / 1465_try_activate_fallback() switches provider → deepseek, then continues — without rebuilding api_messages.
  3. L1186_build_api_kwargs(api_messages) re-sends the stale, Codex-shaped messages to DeepSeek, which requires reasoning_content on every assistant turn → 400.

The pad logic itself is correct — it simply never re-runs after the provider switch.

Reproduction

Rebuild the same conversation history under each provider and POST to DeepSeek:

  • Built with provider=deepseek active → all assistant turns padded → DeepSeek 200 (continues normally, makes a tool call).
  • Built with provider=openai-codex active, then sent to DeepSeek (the failover path) → bare assistant turns remain → DeepSeek 400 "reasoning_content must be passed back".

In a captured real failure: 33-message history, 14 assistant tool-call turns, 5 of them bare → 400.

Relationship to existing issues

This is the require-side sibling of two open Codex-failover bugs — all share the root "messages built for the primary survive unchanged into the fallback request":

  • #29205 (P1) — Codex→Anthropic: Codex reasoning-only turns become empty assistant prefill → 400
  • #32617 — Codex→xAI: Codex encrypted_content replayed, can't decrypt → 400
  • #27297 — make reasoning_content echo-back detection dynamic (detection layer; orthogonal)

The open strip PRs (#24435 etc.) address the reject-side (providers that don't accept reasoning). They do not help here — DeepSeek requires the field; it must be re-padded, not stripped. I could find no existing issue for this require-side failover variant.

Proposed fix

Re-apply the echo-back pad for the current provider immediately before building the request kwargs, so a fallback to a require-side provider re-pads the history that was built under the primary. Idempotent and a no-op unless the active provider enforces echo-back, so all current and future fallback paths are covered by one call. PR attached.

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