[codex] fix dflash malformed final recovery - #36065
Conversation
|
Added follow-up commit eb3ca72e to cover the 2026-05-31 14:04 PDT live dflash/TUI failure where a post-tool final ended on the open connector Validation: |
mxnstrexgl
left a comment
There was a problem hiding this comment.
LGTM — automated review passed. No security, quality, or test coverage issues detected.
2d9ff37 to
3bfad9f
Compare
3bfad9f to
ba92920
Compare
ba92920 to
d06a7ba
Compare
|
Refreshed this branch onto current upstream main as a single refresh commit (transplant — the branch predated the history replacement, so there was no merge base to rebase across). The change is identical to the original diff except for one conflict resolution in |
Reapply the malformed-tool-final-response recovery (detector, per-turn retry counter, one recovery reprompt, fallback escalation, terminal sentinel, and the two run_agent test files) onto the post-history-replacement mainline without carrying any stale branch history. Conflict resolution: main's one-shot _emit_pending_fallback_notice() now fires after the malformed-final check, at the genuinely-successful-content point, so its successful-recovery/terminal- failure semantics hold for the new recovery paths too. Refresh of PR NousResearch#36065; original head ba929207270ed97efbd8dcb10f4a9a50dcadf396.
d06a7ba to
5a111cb
Compare
|
Refreshed onto current upstream/main (was 106 commits behind). Clean rebase of the existing single-commit refresh (d06a7bae04) — zero conflicts, no hand edits needed; the 6-file/348-insertion diff is byte-identical to the prior refresh. Original PR head (pre-refresh): |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused malformed-final recovery and the regression coverage. Current main still accepts non-empty post-tool finish_reason="stop" text through agent/conversation_loop.py:5089-5097, so the underlying failure mode remains relevant.
Problems
- The fallback branch at
agent/conversation_loop.py:5118calls_try_activate_fallback()but does not synchronize the already-builtapi_messages._sync_failover_system_message()exists specifically for this case (agent/conversation_loop.py:497-520), and every current fallback path invokes it.try_activate_fallback()rewrites the cached prompt identity atagent/chat_completion_helpers.py:1638-1640; without the sync, the fallback request can retain the primary identity. tests/run_agent/test_malformed_final_recovery.py:139-182verifies the fallback model and output but not the fallback request's system-message identity.
Suggested changes
- Mirror the adjacent empty-response fallback path (
agent/conversation_loop.py:5027-5030) by syncingactive_system_promptandapi_messagesafter fallback activation. - Add an assertion for the fallback request's system prompt in the malformed-final fallback regression.
Automated hermes-sweeper review.
| "⚠️ Malformed final response repeated — " | ||
| "switching to fallback provider..." | ||
| ) | ||
| if agent._try_activate_fallback(): |
There was a problem hiding this comment.
Please synchronize the in-flight api_messages after this fallback, as every existing fallback branch does via _sync_failover_system_message(...). _try_activate_fallback() rewrites the cached Model/Provider identity, but this call block reuses the pre-fallback API message list; without the sync the fallback request can carry stale identity.
|
Thanks for the deep investigation here — the root-cause analysis (visible degenerate text with Closing on policy rather than quality. The trigger is a heuristic content classifier over model output (trailing repeated-punctuation runs, open connector words) — that's open-ended coverage for degenerate output from a specific weak model, and the set of ways a bad model mangles a final response grows forever, each variant needing a new branch. We hold the line that we don't add recovery layers that absorb bad model outputs: the existing empty-response recovery stays because "zero content" is an objective wire-level failure signal, but "this text looks degenerate" is a quality judgment we don't want the agent loop making. The right fix for dflash-shaped finals is upstream (model choice / provider), not detection heuristics in the conversation loop. Two pieces of this PR are independently valuable and NOT covered by the policy close — you're welcome to resubmit them standalone:
Appreciate the work — this was a high-quality PR that happens to sit on the wrong side of a deliberate line. |
Summary
finish_reason=stopbut ends with degenerate repeated punctuationRoot Cause
The dflash turn did not fail as an empty response or stream timeout. It returned visible text with
finish_reason=stop, so Hermes treated the corrupted final as successful and triggered the background skill-review loop. The skill reviewer then learned from the foreground assistant's unsupported diagnosis.Validation
python3 -m pytest -q -o addopts='' tests/run_agent/test_malformed_final_recovery.py tests/run_agent/test_empty_response_recovery_persistence.py tests/run_agent/test_background_review.py::test_background_review_installs_auto_deny_approval_callback tests/run_agent/test_background_review.py::test_background_review_summary_is_attributed_to_self_improvement_loop tests/run_agent/test_partial_stream_finish_reason.py tests/run_agent/test_provider_fallback.py tests/run_agent/test_retry_status_buffer.pypython3 -m compileall -q run_agent.py agent/conversation_loop.py agent/background_review.py tests/run_agent/test_malformed_final_recovery.pygit diff --checkFork mirror: OmarB97#49
Closed dirty mirror superseded by #49: OmarB97#48
Note: local pytest needed
-o addopts=''because this shell lacks the project timeout plugin.