fix(codex): rescue reasoning-only turns that die with 'remained incomplete after 3 continuation attempts' - #63690
Closed
IpastorSan wants to merge 1 commit into
Closed
IpastorSan wants to merge 1 commit into
IpastorSan wants to merge 1 commit into
Conversation
…plete after 3 continuation attempts' grok-4.x on the xAI /v1/responses surface sometimes ends a turn with only reasoning items — no message output item, no tool calls — and those reasoning items carry no encrypted_content. Two compounding problems: 1. The model occasionally emits its final answer INSIDE the reasoning channel, delimited by grok's internal "<response>" tag. The answer exists but is classified reasoning-only → finish_reason=incomplete. 2. An interim assistant message holding only plain-text reasoning replays as nothing in _chat_messages_to_responses_input, so every continuation request is byte-identical to the one that just failed. The model deterministically repeats the reasoning-only response until the retry budget is exhausted and the turn dies with "Codex response remained incomplete after 3 continuation attempts". Fixes: - _normalize_codex_response (xai_responses only): salvage the <response>-delimited tail from the reasoning text and promote it to assistant content; the untagged prefix stays as thinking text. - Codex-incomplete continuation path: when the interim message has nothing the input converter will replay (no content, no encrypted reasoning items, no message items), append a user-role nudge so the retry actually differs and explicitly asks for the final answer / pending tool call. Mirrors the existing _get_continuation_prompt pattern used for length truncation. Observed live with grok-4.20 on xai-oauth (2026-07-13); sibling of the grok-composer web_search incomplete-loop fix in transports/codex.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
teknium1
added a commit
that referenced
this pull request
Jul 15, 2026
…lations Follow-up to the salvaged #63690: when the interim assistant message is too empty to append (no content and no reasoning of any kind), the last message in history is still the prior user/tool turn — appending the user-role nudge there would create a user→user or tool→user sequence that strict providers reject. Only append the nudge when the last message is an assistant turn. Also add IpastorSan to AUTHOR_MAP.
Collaborator
|
Merged via PR #64768 — your commit was cherry-picked onto current main with your authorship preserved in git log (rebase merge). We added a small follow-up: an alternation guard on the continuation nudge so an interim turn too empty to append can never produce a user→user / tool→user sequence on strict providers. Excellent PR — the two-cause analysis (answer in the reasoning channel + byte-identical retries from unreplayable interims) and the live grok-4.20 observations made this a straightforward salvage. |
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…lations Follow-up to the salvaged NousResearch#63690: when the interim assistant message is too empty to append (no content and no reasoning of any kind), the last message in history is still the prior user/tool turn — appending the user-role nudge there would create a user→user or tool→user sequence that strict providers reject. Only append the nudge when the last message is an assistant turn. Also add IpastorSan to AUTHOR_MAP.
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…lations Follow-up to the salvaged NousResearch#63690: when the interim assistant message is too empty to append (no content and no reasoning of any kind), the last message in history is still the prior user/tool turn — appending the user-role nudge there would create a user→user or tool→user sequence that strict providers reject. Only append the nudge when the last message is an assistant turn. Also add IpastorSan to AUTHOR_MAP.
prmartinow
pushed a commit
to prmartinow/hermes-agent
that referenced
this pull request
Aug 26, 2026
…lations Follow-up to the salvaged NousResearch#63690: when the interim assistant message is too empty to append (no content and no reasoning of any kind), the last message in history is still the prior user/tool turn — appending the user-role nudge there would create a user→user or tool→user sequence that strict providers reject. Only append the nudge when the last message is an assistant turn. Also add IpastorSan to AUTHOR_MAP.
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
…lations Follow-up to the salvaged NousResearch#63690: when the interim assistant message is too empty to append (no content and no reasoning of any kind), the last message in history is still the prior user/tool turn — appending the user-role nudge there would create a user→user or tool→user sequence that strict providers reject. Only append the nudge when the last message is an assistant turn. Also add IpastorSan to AUTHOR_MAP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On the xAI
/v1/responsessurface (xai-oauth), grok-4.x sometimes ends a turn with only reasoning items — nomessageoutput item, no tool calls. When that happens, the turn deterministically dies with:Two compounding causes:
The answer can land in the reasoning channel. The model occasionally emits its final answer inside the reasoning item, delimited by grok's internal
<response>tag. The answer exists, but_normalize_codex_responseclassifies the turn as reasoning-only →finish_reason="incomplete".Continuations are a no-op. grok's reasoning items on this surface carry no
encrypted_content, so the interim assistant message (plain-text reasoning only, no content, no replayable items) emits nothing in_chat_messages_to_responses_input. Every continuation request is therefore byte-identical to the one that just came back incomplete — the model repeats the reasoning-only response until the retry budget is exhausted.Observed live with grok-4.20 on xai-oauth (2026-07): the failure pattern shows up as triplets of
finish_reason="incomplete"assistant messages with populatedreasoningand emptycontent, across multiple unrelated sessions in ordinary usage. This is a sibling of the grok-composer web_search incomplete-loop fix already documented intransports/codex.py.Related issues
Fix
_normalize_codex_response(scoped toissuer_kind == "xai_responses"): when a turn has no visible content and no tool calls but the reasoning text contains grok's<response>delimiter, promote the delimited tail to assistant content (the untagged prefix stays as thinking text). The turn completes normally on the first attempt.conversation_loop.py): when the interim message has nothing the input converter will replay (no content, no encrypted reasoning items, no message items), append a user-role nudge so the retry actually differs and explicitly asks for the final answer / pending tool call. Mirrors the existing_get_continuation_promptpattern used for length truncation, and is guarded against duplicate insertion. Turns whose interim is replayable (visible content, encrypted reasoning) are untouched.Tests
tests/agent/test_codex_responses_adapter.py: salvage with/without closing tag, xAI-only scoping, no-marker case stays incomplete.tests/run_agent/test_run_agent_codex_responses.py: nudge is appended exactly once after an unreplayable reasoning-only interim (and the turn then completes); nudge does NOT fire for a replayable interim.scripts/run_tests.sh tests/agent/test_codex_responses_adapter.py tests/run_agent/test_run_agent_codex_responses.py(hermetic CI wrapper): 114 passed, 0 failed.scripts/check-windows-footguns.py --diff: clean (change is pure string/dict handling, no OS-specific code).Platforms tested
🤖 Generated with Claude Code