Skip to content

fix(codex): rescue reasoning-only turns from the 3-attempt continuation death loop - #64768

Merged
teknium1 merged 2 commits into
mainfrom
fix/63690-xai-reasoning-rescue
Jul 15, 2026
Merged

fix(codex): rescue reasoning-only turns from the 3-attempt continuation death loop#64768
teknium1 merged 2 commits into
mainfrom
fix/63690-xai-reasoning-rescue

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

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 no encrypted_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 to issuer_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)
  • Follow-up (ours): alternation guard on the nudge — only append it when the last message is an assistant turn, so an interim too empty to append can never produce a user→user / tool→user sequence that strict providers reject; IpastorSan added to AUTHOR_MAP

Validation

Before After
grok answer inside <response> delimiter incomplete → 3 dead retries → error answer promoted, turn completes on attempt 1
reasoning-only, no marker, unreplayable 3 byte-identical retries nudged retry differs, model produces answer
replayable interim (encrypted reasoning) untouched untouched
  • scripts/run_tests.sh tests/agent/test_codex_responses_adapter.py tests/run_agent/test_run_agent_codex_responses.py: 114/114 passed
  • E2E with real imports: marker promotion, no-marker continuation, and non-xAI scoping all verified

Complements #64764 (which trusts response.status for unrecognized backends; xAI stays on the continuation path this PR makes actually work).

Infographic

reasoning-only-turn-rescue

IpastorSan and others added 2 commits July 14, 2026 21:51
…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.
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/xai xAI (Grok) codex P2 Medium — degraded but workaround exists labels Jul 15, 2026
@teknium1
teknium1 merged commit 2fc0e3d into main Jul 15, 2026
31 checks passed
@teknium1
teknium1 deleted the fix/63690-xai-reasoning-rescue branch July 15, 2026 07:13
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/xai xAI (Grok) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants