fix(codex): rescue reasoning-only turns from the 3-attempt continuation death loop - #64768
Merged
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>
…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.
This was referenced Jul 15, 2026
teknium1
added a commit
that referenced
this pull request
Jul 15, 2026
) test_normalize_codex_response_salvage_is_xai_scoped broke on main when two same-day merges crossed: #64764 (#64434 — trust response.status for reasoning-only turns on UNRECOGNIZED Responses backends) changed what a bare _normalize_codex_response(response) call returns for status='completed' reasoning-only output (now 'stop'), while #64768 added this test calling with no issuer_kind and expecting 'incomplete'. The test's intent is that the xAI reasoning-channel salvage does not leak into other special-cased backends — pin issuer_kind='codex_backend' so it exercises exactly that (same pattern as test_normalize_codex_response_treats_summary_only_reasoning_as_incomplete, which was already pinned for #64434).
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…sResearch#64844) test_normalize_codex_response_salvage_is_xai_scoped broke on main when two same-day merges crossed: NousResearch#64764 (NousResearch#64434 — trust response.status for reasoning-only turns on UNRECOGNIZED Responses backends) changed what a bare _normalize_codex_response(response) call returns for status='completed' reasoning-only output (now 'stop'), while NousResearch#64768 added this test calling with no issuer_kind and expecting 'incomplete'. The test's intent is that the xAI reasoning-channel salvage does not leak into other special-cased backends — pin issuer_kind='codex_backend' so it exercises exactly that (same pattern as test_normalize_codex_response_treats_summary_only_reasoning_as_incomplete, which was already pinned for NousResearch#64434).
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…sResearch#64844) test_normalize_codex_response_salvage_is_xai_scoped broke on main when two same-day merges crossed: NousResearch#64764 (NousResearch#64434 — trust response.status for reasoning-only turns on UNRECOGNIZED Responses backends) changed what a bare _normalize_codex_response(response) call returns for status='completed' reasoning-only output (now 'stop'), while NousResearch#64768 added this test calling with no issuer_kind and expecting 'incomplete'. The test's intent is that the xAI reasoning-channel salvage does not leak into other special-cased backends — pin issuer_kind='codex_backend' so it exercises exactly that (same pattern as test_normalize_codex_response_treats_summary_only_reasoning_as_incomplete, which was already pinned for NousResearch#64434).
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.
Summary
Reasoning-only turns on the xAI Responses surface no longer die with "Codex response remained incomplete after 3 continuation attempts" — grok's answer is salvaged out of the reasoning channel, and unreplayable continuation retries get a one-shot nudge so they stop being byte-identical no-ops.
Salvages #63690 (@IpastorSan) with authorship preserved, plus one follow-up.
Two compounding root causes: (1) grok-4.x sometimes emits its final answer inside the reasoning item behind its internal
<response>delimiter, so the adapter classified a finished turn as reasoning-only →incomplete; (2) grok's reasoning items on this surface carry noencrypted_content, so the interim message replays as nothing and every continuation request was byte-identical to the one that just failed — 3 guaranteed-identical retries, then the error.Changes
agent/codex_responses_adapter.py: scoped toissuer_kind == "xai_responses"— promote the<response>-delimited tail of the reasoning text to assistant content; the untagged prefix stays as thinking text (@IpastorSan)agent/conversation_loop.py: when the interim message has nothing the Responses input converter will replay, append a one-shot user-role nudge asking for the final answer so the retry actually differs; deduplicated against double-insertion (@IpastorSan)Validation
<response>delimiterscripts/run_tests.sh tests/agent/test_codex_responses_adapter.py tests/run_agent/test_run_agent_codex_responses.py: 114/114 passedComplements #64764 (which trusts
response.statusfor unrecognized backends; xAI stays on the continuation path this PR makes actually work).Infographic