From 7026af4e23030a1c01a388ac60575bbf3b011187 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 11 May 2026 07:37:22 -0700 Subject: [PATCH] fix(agent): catch ChatGPT-account Codex data-URL rejection so images are stripped instead of cascading to compression (#23602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- run_agent.py | 15 ++++++++++++ .../test_image_rejection_fallback.py | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/run_agent.py b/run_agent.py index cb39669ece7..8908562b38c 100644 --- a/run_agent.py +++ b/run_agent.py @@ -13117,6 +13117,21 @@ class AIAgent: "does not support multimodal", "does not support vision", "model does not support image", + # ChatGPT-account Codex backend + # (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, but got a value with an + # invalid format." The OpenAI Responses API on the + # public endpoint accepts data URLs, but the + # ChatGPT-account variant does not. Without this + # phrase the agent cascaded into compression / + # context-too-large recovery instead of just + # stripping the images. Match is narrow on + # purpose — keyed on the field-path apostrophe so + # we don't false-trip on other URL validation + # errors. (issue #23570) + "image_url'. expected", ) _err_lower = _err_body.lower() _looks_like_image_rejection = any( diff --git a/tests/run_agent/test_image_rejection_fallback.py b/tests/run_agent/test_image_rejection_fallback.py index e52719d9742..d1d6c7ff028 100644 --- a/tests/run_agent/test_image_rejection_fallback.py +++ b/tests/run_agent/test_image_rejection_fallback.py @@ -194,6 +194,7 @@ class TestImageRejectionPhraseIsolation: "does not support multimodal", "does not support vision", "model does not support image", + "image_url'. expected", ) def _matches(self, body: str) -> bool: @@ -238,6 +239,29 @@ class TestImageRejectionPhraseIsolation: "This model does not support images", "vision is not supported on this endpoint", "model does not support image input", + # ChatGPT-account Codex backend (issue #23570) — rejects + # data:image/...base64 URLs in input_image fields. Without this + # match the agent cascaded into compression / context-too-large + # recovery instead of just stripping the images. + "Invalid 'input[56].content[1].image_url'. Expected a valid URL, but got a value with an invalid format.", ] for body in bodies: assert self._matches(body) is True, f"false negative on: {body}" + + def test_codex_data_url_rejection_does_not_false_match_other_url_errors(self): + """The narrow 'image_url'. expected' phrase (keyed on the + field-path apostrophe used in the Codex Responses error format) + must NOT trip on URL validation errors that aren't about + image_url specifically. See issue #23570 for the original error. + """ + bodies = [ + # Generic URL validation errors — should NOT trip + "Invalid webhook_url. Must be a valid URL.", + "Expected a valid URL but got an empty string.", + "redirect_uri does not look like a valid URL.", + # An image_url error worded differently — also should not trip + # the narrow phrase (a separate phrase would be needed) + "image_url field cannot be empty", + ] + for body in bodies: + assert self._matches(body) is False, f"false positive on: {body}"