From 9818b9a1acb915971d835d1faa85949e9f7a87a5 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 15 May 2026 17:15:22 -0700 Subject: [PATCH] fix(xai-oauth): rewrite entitlement-403 hint to not accuse subscribers (#26666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #26644 confidently told users "xAI OAuth account lacks SuperGrok / X Premium entitlement" on any 403 from xAI's permission-denied surface. But that body is returned for at least four distinct causes that Hermes cannot distinguish from the wire: * Account has no Grok subscription at all * Account has SuperGrok but the tier doesn't include the requested model (e.g. grok-4.3 needs SuperGrok Heavy) * Monthly quota for the subscribed tier is exhausted * SuperGrok is active but the API access add-on isn't enabled Don Piedro pushed back that he IS subscribed yet still hit this. Picking the worst-case interpretation ("you're not subscribed") reads as wrong and insulting to subscribers, and points them at a fix they already did. New wording lists all 4 possibilities and points at https://grok.com/?_s=usage where the user can check which applies. The detection logic and credential-pool short-circuit (PR #26664) are unchanged — only the user-facing wording is rephrased. --- run_agent.py | 42 ++++++++++++------ .../test_codex_xai_oauth_recovery.py | 44 +++++++++++++++++-- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/run_agent.py b/run_agent.py index da47ca84e34..da05e7e8239 100644 --- a/run_agent.py +++ b/run_agent.py @@ -5006,23 +5006,35 @@ class AIAgent: @staticmethod def _decorate_xai_entitlement_error(detail: str) -> str: - """Append a friendly hint when xAI's OAuth surface returns an - entitlement-shaped error. + """Append a neutral hint when xAI's OAuth surface returns the + permission-denied 403. - xAI's ``/v1/responses`` endpoint replies to OAuth tokens that lack a - SuperGrok / X Premium subscription with HTTP 403 carrying a body like:: + xAI's ``/v1/responses`` endpoint replies to several distinct failure + modes with the SAME body:: {"code": "The caller does not have permission to execute the specified operation", "error": "You have either run out of available resources or do not have an active Grok subscription. - Manage subscriptions at https://grok.com/..."} + Manage subscriptions at https://grok.com/?_s=usage or subscribe + at https://grok.com/supergrok"} - The raw text is useful but the action the user needs to take (subscribe - on grok.com, or switch providers with ``/model``) isn't obvious from - the wire format. Detect the entitlement shape and append a hint. + That body covers at least four real causes we cannot distinguish + without more info from xAI: - Matched once per detail string — won't double-decorate if the upstream - already concatenated the same text. + * Account has no Grok subscription at all + * Account has SuperGrok but the tier doesn't include the requested + model (e.g. grok-4.3 needs SuperGrok Heavy) + * Monthly quota for the subscribed tier is exhausted (the + ``?_s=usage`` URL hints at this) + * SuperGrok is active but the API access add-on isn't enabled + + Picking one ("you're not subscribed") is wrong for the other three + and reads as insulting to subscribers. Surface the raw xAI text + verbatim and point at https://grok.com/?_s=usage where the user + can see WHICH of those four it is. + + Matched once per detail string — won't double-decorate if the + upstream already concatenated the same text. """ if not detail: return detail @@ -5035,11 +5047,15 @@ class AIAgent: if not is_entitlement: return detail hint = ( - " — xAI OAuth account lacks SuperGrok / X Premium entitlement for " - "this model. Subscribe at https://grok.com or run `/model` to " + " — xAI rejected the request on this OAuth account. Could be a " + "missing subscription, a tier that doesn't include this model, an " + "exhausted quota, or API access not enabled. Check " + "https://grok.com/?_s=usage to see which, or run `/model` to " "switch providers." ) - if hint.strip() in detail: + # Idempotency: detect prior decoration by a substring unique to the + # hint (not present in xAI's own body text). + if "Could be a missing subscription" in detail: return detail return f"{detail}{hint}" diff --git a/tests/run_agent/test_codex_xai_oauth_recovery.py b/tests/run_agent/test_codex_xai_oauth_recovery.py index 7c675f22225..c64f46eea09 100644 --- a/tests/run_agent/test_codex_xai_oauth_recovery.py +++ b/tests/run_agent/test_codex_xai_oauth_recovery.py @@ -163,7 +163,12 @@ def test_codex_stream_postlude_error_still_falls_back(): def test_summarize_api_error_decorates_xai_entitlement_403(): - """xAI's OAuth 403 must end with the subscribe-or-switch hint.""" + """xAI's OAuth 403 must end with the neutral 4-cause hint. + + Wording is deliberately ambiguous because xAI returns the SAME body for: + no subscription, wrong tier, exhausted quota, or API access not enabled. + Picking one (e.g. "you're not subscribed") would insult subscribers. + """ from run_agent import AIAgent error = RuntimeError( @@ -173,10 +178,39 @@ def test_summarize_api_error_decorates_xai_entitlement_403(): "subscriptions at https://grok.com'}" ) summary = AIAgent._summarize_api_error(error) + # The original xAI text must survive — it's still useful diagnostic info. assert "do not have an active Grok subscription" in summary - assert "SuperGrok" in summary + # The hint must NOT confidently assert "lacks subscription"; it must + # acknowledge the 4 possible causes. + assert "Could be a missing subscription" in summary + assert "tier that doesn't include this model" in summary + assert "exhausted quota" in summary + assert "API access not enabled" in summary + # The hint must point at the usage page where the user can verify which. + assert "https://grok.com/?_s=usage" in summary + # Switching providers is still a valid escape hatch. assert "/model" in summary - assert "https://grok.com" in summary + + +def test_summarize_api_error_does_not_accuse_subscribers(): + """Hint must not confidently say the user has no subscription. + + Don Piedro reported his subscription is active. The hint must not + contradict him — it must list all 4 possible causes and let him + check which one applies. + """ + from run_agent import AIAgent + + error = RuntimeError( + "HTTP 403: do not have an active Grok subscription" + ) + summary = AIAgent._summarize_api_error(error) + # MUST NOT contain language that assumes the user is unsubscribed. + assert "lacks SuperGrok" not in summary + assert "lacks subscription" not in summary + assert "your account doesn't have" not in summary.lower() + # MUST contain the neutral framing. + assert "Could be" in summary or "could be" in summary def test_summarize_api_error_decorates_xai_body_message(): @@ -197,7 +231,7 @@ def test_summarize_api_error_decorates_xai_body_message(): summary = AIAgent._summarize_api_error(_XaiErr("403")) assert "HTTP 403" in summary - assert "SuperGrok / X Premium" in summary + assert "Could be a missing subscription" in summary def test_summarize_api_error_idempotent_for_entitlement_hint(): @@ -208,6 +242,8 @@ def test_summarize_api_error_idempotent_for_entitlement_hint(): once = AIAgent._decorate_xai_entitlement_error(raw) twice = AIAgent._decorate_xai_entitlement_error(once) assert once == twice + # Sanity: the hint did fire on the first pass. + assert "Could be a missing subscription" in once def test_summarize_api_error_passes_through_unrelated_errors():