fix(xai-oauth): split 403 (tier/entitlement) from 400/401 in token endpoint

xAI's token endpoint returns HTTP 403 to the OAuth grant when the
account isn't on the allowlist for API access (e.g. standard
SuperGrok subscribers — see #26847). Treating it like a stale-token
400/401 made ``format_auth_error`` append "Run ``hermes model`` to
re-authenticate", which is misleading because re-login can't change
xAI's tier decision.

Split 403 off in both ``refresh_xai_oauth_pure`` and the loopback
login token exchange:

* New error code ``xai_oauth_tier_denied`` with ``relogin_required=False``
* Message explains the entitlement gate and points at the
  ``XAI_API_KEY`` + ``provider: xai`` fallback
* 400/401 still set ``relogin_required=True`` as before
* 5xx still set ``relogin_required=False`` as before
This commit is contained in:
xxxigm 2026-05-16 16:19:57 +07:00 committed by Teknium
parent ea49b38625
commit 60ef368792

View file

@ -3443,12 +3443,34 @@ def refresh_xai_oauth_pure(
)
if response.status_code != 200:
detail = response.text.strip()
# ``403`` from xAI's token endpoint is almost always a tier /
# entitlement gate (the OAuth grant exists but the account isn't
# on the allowlist for API access). Re-running ``hermes model``
# won't fix that — surface a separate error code so
# ``format_auth_error`` doesn't append a misleading
# re-authenticate hint, and point users at the ``XAI_API_KEY``
# fallback. See #26847.
if response.status_code == 403:
raise AuthError(
"xAI token refresh failed with HTTP 403."
+ (f" Response: {detail}" if detail else "")
+ " This OAuth account is not authorized for xAI API"
" access — xAI may be restricting API/OAuth use to"
" specific SuperGrok tiers despite the in-app"
" subscription being active. Re-logging in won't"
" change that; set ``XAI_API_KEY`` and switch to"
" ``provider: xai`` (API-key path) if available, or"
" upgrade your subscription at https://x.ai/grok.",
provider="xai-oauth",
code="xai_oauth_tier_denied",
relogin_required=False,
)
raise AuthError(
"xAI token refresh failed."
+ (f" Response: {detail}" if detail else ""),
provider="xai-oauth",
code="xai_refresh_failed",
relogin_required=(response.status_code in {400, 401, 403}),
relogin_required=(response.status_code in {400, 401}),
)
try:
payload = response.json()
@ -6225,6 +6247,25 @@ def _xai_oauth_exchange_code_for_tokens(
if response.status_code != 200:
body = response.text.strip()
# See ``refresh_xai_oauth_pure`` — token-exchange 403 also
# surfaces tier/entitlement gating from xAI's backend. Avoid
# the misleading "re-authenticate" hint and point at the API
# key fallback. See #26847.
if response.status_code == 403:
raise AuthError(
f"xAI token exchange failed (HTTP 403)."
+ (f" Response: {body}" if body else "")
+ " This OAuth account is not authorized for xAI API"
" access — xAI may be restricting API/OAuth use to"
" specific SuperGrok tiers despite the in-app"
" subscription being active. Set ``XAI_API_KEY``"
" and switch to ``provider: xai`` (API-key path) if"
" available, or upgrade your subscription at"
" https://x.ai/grok.",
provider="xai-oauth",
code="xai_oauth_tier_denied",
relogin_required=False,
)
raise AuthError(
f"xAI token exchange failed (HTTP {response.status_code})."
+ (f" Response: {body}" if body else ""),