Skip to content

fix(agent): handle content_filter in Codex incomplete response handling - #55682

Closed
AlexFucuson9 wants to merge 1 commit into
NousResearch:mainfrom
AlexFucuson9:fix/codex-content-filter-incomplete
Closed

fix(agent): handle content_filter in Codex incomplete response handling#55682
AlexFucuson9 wants to merge 1 commit into
NousResearch:mainfrom
AlexFucuson9:fix/codex-content-filter-incomplete

Conversation

@AlexFucuson9

Copy link
Copy Markdown
Contributor

Summary

Handle content_filter as an incomplete reason in Codex Responses mode so content-policy refusals are properly classified instead of being treated as normal completions.

Problem (P2 #55637)

Codex Responses can return status="incomplete" with incomplete_details.reason="content_filter" and no output items. The existing code only handled max_output_tokens/length as incomplete reasons — content_filter fell through to the else branch and was treated as finish_reason="stop", causing the response to be processed as a normal completion instead of a content-policy refusal.

This means:

  1. The refusal is silently accepted as a valid response
  2. No failover to a fallback model is attempted
  3. The user sees an empty or nonsensical response

Fix

Add explicit check for incomplete_reason == "content_filter" to set finish_reason = "content_filter", which routes through the existing content-policy refusal handling (failover, retry with fallback model).

Changes

  • agent/conversation_loop.py: Add elif branch for content_filter (2 lines added)

Fixes #55637

Codex Responses can return status='incomplete' with
incomplete_details.reason='content_filter' and no output items. The
existing code only handled 'max_output_tokens'/'length' as incomplete
reasons — content_filter fell through to the else branch and was
treated as finish_reason='stop', causing the response to be processed
as a normal completion instead of a content-policy refusal.

Add explicit check for incomplete_reason == 'content_filter' to set
finish_reason='content_filter', which routes through the existing
content-policy refusal handling (failover, retry with fallback model).

Fixes NousResearch#55637
@rodriguez46p-ui

Copy link
Copy Markdown

Code Review Summary for PR #55682

PR: #55682
Title: fix(agent): handle content_filter in Codex incomplete response handling
Head: 93ada95

Verdict: Changes requested

Blocking

  • agent/conversation_loop.py:1478 — The new finish_reason = "content_filter" branch still calls normalize_response(response) in the content-policy handling block. For the bug described in the PR body (status="incomplete", incomplete_details.reason="content_filter", and no output items), the Codex Responses normalizer raises RuntimeError("Responses API returned no output items") before fallback/refusal handling can run. So the reported empty-output content-filter case is still not routed through FailoverReason.content_policy_blocked.

    Reproduction run in a detached worktree:

    RuntimeError: Responses API returned no output items
    

    The fix likely needs to avoid normalizing an empty-output content-filter response, or make _normalize_codex_response recognize response-level content-filter incompletes before the empty-output guard. Please add a focused regression test for the exact no-output shape from [Bug]: Codex incomplete content_filter Responses are handled as incomplete retries #55637.

Checks run

  • git diff --stat refs/remotes/origin/main...HEAD -> 1 file changed, 2 insertions.
  • python -m py_compile agent/conversation_loop.py agent/codex_responses_adapter.py -> passed.
  • Ad-hoc no-output Codex Responses content-filter probe -> reproduced the remaining blocker above.
  • git diff --check refs/remotes/origin/main...HEAD -> passed.

@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/openai OpenAI / Codex Responses API P2 Medium — degraded but workaround exists labels Jun 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Competing fix for #55637. This PR (conversation_loop.py only, +2 lines) is a subset of the broader open PR #55639, which also patches transports/codex.py (accept empty-output content-filter as a valid terminal) and codex_responses_adapter.py (normalize to finish_reason=content_filter) plus regression tests. #55639 looks canonical here (earlier, full transport/adapter coverage). Flagging the cluster so a maintainer can pick — not closing either. Related: #55639, #27988 (closed Azure-Foundry incomplete-mapping sibling).

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the missing Codex reason mapping. The loop-only change does not handle the exact empty-output response in the report.

Problems

  • agent/conversation_loop.py:1373-1420 validates Codex output before the finish-reason block and marks an empty output invalid, so this branch is not reached for the reported shape.
  • If that gate is relaxed, the existing refusal path calls normalize_response() at agent/conversation_loop.py:1669-1677; _normalize_codex_response() raises on empty output at agent/codex_responses_adapter.py:1121-1138 before it can surface a refusal.
  • No regression test covers status="incomplete", incomplete_details.reason="content_filter", and output=[].

Suggested changes

  • Handle the no-output content-filter response in validation and normalization as well as in the loop, and add focused transport/adapter/loop coverage. The broader approach in #55639 covers those layers.

Automated hermes-sweeper review.

incomplete_reason = getattr(incomplete_details, "reason", None)
if status == "incomplete" and incomplete_reason in {"max_output_tokens", "length"}:
finish_reason = "length"
elif status == "incomplete" and incomplete_reason == "content_filter":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mapping alone does not reach the reported empty-output case: current main validates Codex output before finish-reason handling (agent/conversation_loop.py:1373-1420) and rejects output=[]. If that validation is relaxed, the refusal path also needs _normalize_codex_response() to accept this response instead of raising on empty output.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Closing as a duplicate of #55639 by @sk-holmes, which was submitted first (about 100 minutes earlier) and has been merged via salvage PR #65061. Your conversation-loop branch matched the same classification idea, but the merged fix also covers the transport validation gap — the output=[] refusal shape never reaches the loop branch without it. Thanks for jumping on the issue; the community signal helped confirm the failure shape quickly.

@teknium1 teknium1 closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/openai OpenAI / Codex Responses API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Codex incomplete content_filter Responses are handled as incomplete retries

4 participants