Skip to content

fix(agent): catch ChatGPT-account Codex data-URL rejection so images are stripped instead of cascading to compression#23602

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-04bc4ccd
May 11, 2026
Merged

fix(agent): catch ChatGPT-account Codex data-URL rejection so images are stripped instead of cascading to compression#23602
teknium1 merged 1 commit into
mainfrom
hermes/hermes-04bc4ccd

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Closes part of #23570 (image-rejection cascade on Codex ChatGPT-account backend).

Summary

ChatGPT-account Codex (https://chatgpt.com/backend-api/codex) rejects data:image/...base64 URLs in input_image fields with HTTP 400 "Invalid 'input[N].content[K].image_url'. Expected a valid URL." Hermes' phrase list didn't recognize this wording, so the error escaped the strip-and-retry branch and cascaded into compression / context-too-large recovery — which on a depleted-OpenRouter setup turned into the 402 storm in #23570.

Changes

  • run_agent.py: added one narrow phrase "image_url'. expected" to _IMAGE_REJECTION_PHRASES. Match is keyed on the field-path apostrophe so it doesn't false-trip on generic "Expected a valid URL" errors from unrelated tools (webhooks, redirect_uri, etc.).
  • tests/run_agent/test_image_rejection_fallback.py: added the real Codex error body to the positive-match list, plus a new test confirming non-image URL errors do NOT trip the new phrase.

Validation

Before After
Codex data-URL HTTP 400 cascades to compression / fallback storm stripped + retried text-only on first hit
False-trip risk on other URL errors n/a tested negative on webhook/redirect/empty-string URL errors
Targeted tests 13 15 (passing)
tests/run_agent/ 1302 passed, 1 unrelated failure 1302 passed, same 1 unrelated failure

The 1 unrelated failure (test_async_httpx_del_neuter::test_same_key_replaces_stale_loop_entry) reproduces on bare origin/main — pre-existing.

Scope note

This is the recovery patch — it makes the already-broken request graceful. The underlying replay-bloat issue (long sessions persist multi-MB base64 in history and re-send it every turn) is architectural and stays open. The fix there is to persist image PATHS and rebuild the data URL at API-send time. Separate PR forthcoming.

Refs #23570 (3 of 3: PR #23585 covered silent config-parse failure, PR #23597 added the auxiliary 402-unhealthy cache; this one catches the specific Codex image-rejection wording so the user's session would degrade gracefully).

…are stripped instead of cascading to compression

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue #23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs #23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-04bc4ccd vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8157 on HEAD, 8157 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4287 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent loop, run_agent.py, prompt builder provider/copilot GitHub Copilot (ACP + Chat) P2 Medium — degraded but workaround exists labels May 11, 2026
@teknium1 teknium1 merged commit 7026af4 into main May 11, 2026
15 of 18 checks passed
@teknium1 teknium1 deleted the hermes/hermes-04bc4ccd branch May 11, 2026 14:37
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
…are stripped instead of cascading to compression (NousResearch#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue NousResearch#23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs NousResearch#23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
…are stripped instead of cascading to compression (NousResearch#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue NousResearch#23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs NousResearch#23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…are stripped instead of cascading to compression (NousResearch#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue NousResearch#23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs NousResearch#23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…are stripped instead of cascading to compression (NousResearch#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue NousResearch#23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs NousResearch#23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
heybenn42 pushed a commit to heybenn42/hermes-agent that referenced this pull request May 15, 2026
* upstream/main: (1099 commits)
  chore: ruff auto-fix C401, C416, C408, PLR1722 (NousResearch#23940)
  feat(prompt-cache): cross-session 1h prefix cache for Claude on Anthropic / OpenRouter / Nous Portal (NousResearch#23828)
  chore: ruff auto-fix PLR6201 — tuple → set in membership tests (NousResearch#23937)
  chore(release): add AUTHOR_MAP entry for wuli666
  fix(auxiliary): evict async wrappers on poisoned client (follow-up to NousResearch#23482)
  fix(cli,tui): align CJK / wide-char markdown tables (NousResearch#23863)
  chore: ruff auto-fixes — collapsible-else-if, if-stmt-min-max, dict.fromkeys (NousResearch#23926)
  fix(/model): surface Nous Portal models from remote catalog manifest (NousResearch#23912)
  fix(cli): defensive _slash_confirm_state access + AUTHOR_MAP
  fix: use TUI modal for slash confirmations
  rebuild model catalog
  fix(dashboard): validate dist exists when --skip-build is set
  fix(dashboard): fallback to stale dist, retry build, add --skip-build flag
  chore: AUTHOR_MAP entry for VinceZcrikl noreply (NousResearch#23647)
  fix: make web UI build output decoding robust on Windows
  fix(agent): catch ChatGPT-account Codex data-URL rejection so images are stripped instead of cascading to compression (NousResearch#23602)
  revert: roll back /goal checklist + /subgoal feature stack (NousResearch#23813)
  chore: AUTHOR_MAP entries for sudo-hardening salvage contributors
  fix(approval): catch sudo with stdin/askpass/shell privilege flags
  fix(terminal): block sudo -S password guessing when SUDO_PASSWORD is not set
  ...
AlexFoxD pushed a commit to AlexFoxD/hermes-agent that referenced this pull request May 21, 2026
…are stripped instead of cascading to compression (NousResearch#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue NousResearch#23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs NousResearch#23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…are stripped instead of cascading to compression (NousResearch#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue NousResearch#23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs NousResearch#23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists provider/copilot GitHub Copilot (ACP + Chat) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants