fix(gateway): suppress hidden-reasoning-only incomplete Codex turns - #65062
Merged
Conversation
… final_response Follow-up to the salvaged #51657: the conversation loop returns the retry-exhaustion sentinel as BOTH final_response and error, so the original detector (which required final_response to be falsy) never fired on real exhaustion turns — the sentinel text was delivered verbatim into the channel, exactly the #51628 poisoning vector. Detect the sentinel echo, blank it before empty-response normalization, and never suppress a turn whose final_response is genuine model text. Also: dedupe-guard mock fix in the test fixture (has_platform_message_id must return False, not a truthy MagicMock) and two guard tests (real answer never suppressed; interrupted/failed never classified).
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
Retry-exhausted Codex turns that produced only hidden reasoning (no visible answer) are no longer posted into gateway channels — the "Codex response remained incomplete after 3 continuation attempts" sentinel stays out of the conversation, where peer agents were re-ingesting it as a completed assistant turn and waking each other up.
Salvages #51657 (@LeonSGP43, fixes #51628) with authorship preserved, plus one substantive follow-up.
Changes
gateway/run.py:_is_gateway_hidden_reasoning_incomplete_turn()detects the exhaustion shape; the sentinel is blanked before delivery, only the user message is persisted to the transcript (mirroring the [Bug] Rate limit (429/timeout) failures silently drop user messages from transcript — #1630 regression #7100 transient-failure rule), and full diagnostics stay in the logs (@LeonSGP43)final_responseto be falsy, but the conversation loop returns the sentinel as BOTHfinal_responseanderror— so the sentinel was delivered verbatim, exactly the poisoning vector. The detector now recognizes the sentinel echo, and a genuinely different final text is never suppressed. Also blanks the sentinel at the delivery site (before empty-response normalization), fixes the test fixture to mirror the real loop shape and thehas_platform_message_iddedupe mock, and adds two guard tests.Validation
scripts/run_tests.shon the new suite + adjacent gateway persistence suites (test_tool_response_drop_recovery,test_empty_model_recovery): 25/25 passedComplements #64768 (which makes exhaustion rarer); this is defense-in-depth for when it still happens.
Infographic