fix(xai-oauth): rewrite entitlement-403 hint to not accuse subscribers (#26666)

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.
This commit is contained in:
Teknium 2026-05-15 17:15:22 -07:00 committed by GitHub
parent ce0e189d3e
commit 9818b9a1ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 69 additions and 17 deletions

View file

@ -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}"

View file

@ -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():