Codex Responses final_answer vs incomplete status mapping
Cluster 27988 · agent. Data as of
Nothing open on GitHub. Fix merged in #45814 on 13 Jun 2026. Check it covers your case.
The short answer
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #45814. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #45814. That is separate from the fix event. - Still open
- Nothing open on GitHub.
The members concern the Codex Responses adapter's handling of a final_answer phase when the top-level response status is incomplete, including a report of finish_reason=incomplete on Azure Foundry. Two members are titled as fixes for this behavior, and the third describes the issue they appear to address.
Open issues0
Open PRs0
Fixes on record
-
#45814 fix(codex-responses): let final_answer complete top-level incomplete responses
files:
agent/codex_responses_adapter.py,tests/run_agent/test_run_agent_codex_responses.pyShow the diff
-
agent/codex_responses_adapter.py@@ -1081,6 +1081,7 @@ def _normalize_codex_response( message_items_raw: List[Dict[str, Any]] = [] tool_calls: List[Any] = [] has_incomplete_items = response_status in {"queued", "in_progress", "incomplete"} + saw_streaming_or_item_incomplete = response_status in {"queued", "in_progress"} saw_commentary_phase = False saw_final_answer_phase = False saw_reasoning_item = False @@ -1095,6 +1096,7 @@ def _normalize_codex_response( if item_status in {"queued", "in_progress", "incomplete"}: has_incomplete_items = True + saw_streaming_or_item_incomplete = True if item_type == "message": item_phase = getattr(item, "phase", None) @@ -1252,7 +1254,9 @@ def _normalize_codex_response( finish_reason = "tool_calls" elif leaked_tool_call_text: finish_reason = "incomplete" - elif has_incomplete_items or (saw_commentary_phase and not saw_final_answer_phase): + elif saw_streaming_or_item_incomplete: + finish_reason = "incomplete" + elif (has_incomplete_items or saw_commentary_phase) and not saw_final_answer_phase: finish_reason = "incomplete" elif (reasoning_items_raw or reasoning_parts or saw_reasoning_item) and not final_text: # Response contains only reasoning (encrypted thinking state and/or -
tests/run_agent/test_run_agent_codex_responses.py@@ -154,6 +154,22 @@ def _codex_ack_message_response(text: str): ) +def _codex_final_answer_with_top_level_incomplete_response(text: str): + return SimpleNamespace( + output=[ + SimpleNamespace( + type="message", + phase="final_answer", + status="completed", + content=[SimpleNamespace(type="output_text", text=text)], + ) + ], + usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6), + status="incomplete", + model="gpt-5.4", + ) + + class _FakeCreateStream: """Iterable-only fake for ``responses.create(stream=True)`` outputs. @@ -1351,6 +1367,92 @@ def test_normalize_codex_response_marks_commentary_only_message_as_incomplete(mo assert "inspect the repository" in (assistant_message.content or "") +def test_normalize_codex_response_final_answer_overrides_top_level_incomplete(monkeypatch): + from agent.codex_responses_adapter import _normalize_codex_response + + assistant_message, finish_reason = _normalize_codex_response( + _codex_final_answer_with_top_level_incomplete_response( + "Briefly:\n\n- I'm Ramsay, your assistant." + ) + ) + + assert finish_reason == "stop" + assert "Ramsay" in (assistant_message.content or "") + + +def test_normalize_codex_response_top_level_incomplete_without_final_answer_stays_incomplete(monkeypatch): + from agent.codex_responses_adapter import _normalize_codex_response + + response = SimpleNamespace( + output=[ + SimpleNamespace( + type="message", + status="completed", + content=[SimpleNamespace(type="output_text", text="Partial...")], + ) + ], + usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6), + status="incomplete", + model="gpt-5.4", + ) + + _, finish_reason = _normalize_codex_response(response) + + assert finish_reason == "incomplete" + + [redacted:email]("top_level_status", ["queued", "in_progress"]) +def test_normalize_codex_response_final_answer_does_not_override_streaming_status( + monkeypatch, top_level_status +): + from agent.codex_responses_adapter import _normalize_codex_response + + response = SimpleNamespace( + output=[ + SimpleNamespace( + type="message", + phase="final_answer", + status="completed", + content=[SimpleNamespace(type="output_text", text="Interim answer.")], + ) + ], + usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6), + status=top_level_status, + model="gpt-5.4", + ) + + _, finish_reason = _normalize_codex_response(response) + + assert finish_reason == "incomplete" + + +def test_normalize_codex_response_final_answer_does_not_override_per_item_in_progress(monkeypatch): + from agent.codex_responses_adapter import _normalize_codex_response + + response = SimpleNamespace( + output=[ + SimpleNamespace( + type="message", + phase="final_answer", + status="completed", + content=[SimpleNamespace(type="output_text", text="Partial final.")], + ), + SimpleNamespace( + type="message", + status="in_progress", + content=[SimpleNamespace(type="output_text", text="")], + ), + ], + usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6), + status="completed", + model="gpt-5.4", + ) + + _, finish_reason = _normalize_codex_response(response) + + assert finish_reason == "incomplete" + + def test_normalize_codex_response_preserves_message_status_for_replay(monkeypatch): """Incomplete Codex output messages must not be replayed as completed.""" agent = _build_agent(monkeypatch)
-
1 PR closed without merging
From the close comments: merged via another change 1
-
#28039 fix(codex-responses): final_answer phase overrides top-level incomplete status (#27988) · merged via another change →
7d11fa4,#45814
Closed issues · 1
- Closed#27988Codex Responses adapter maps complete final_answer to finish_reason=incomplete when top-level response.status is incomplete on Azure Foundry
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #45814
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 13 Jun 2026