# NousResearch/hermes-agent · Codex Responses final_answer vs incomplete status mapping

## Open now

- 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.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #45814. Verify it addresses your case.
- **Recommended:** read PR #45814 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-45814`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #27988: Codex Responses adapter maps complete final_answer to finish_reason=incomplete when top-level response.status is incomplete on Azure Foundry

## Prior & competing PRs

- **#45814: fix(codex-responses): let final_answer complete top-level incomplete responses**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/codex_responses_adapter.py`, `tests/run_agent/test_run_agent_codex_responses.py`
  - fix hunks:
  - `agent/codex_responses_adapter.py`

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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)
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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.
     
    ```

    ```diff
    @@ -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)
    ```
- **#28039: fix(codex-responses): final_answer phase overrides top-level incomplete status (#27988)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `7d11fa4e9`, `#45814`

## Fix status

- Merged to main in PR #45814 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-13`.
- Subsystem: `agent`
