fix(xai-oauth): surface tier-gated 403 with API-key fallback (closes #26847) - #28351
Merged
Conversation
…dpoint 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
…resh-loop
The existing ``_is_entitlement_failure`` heuristic only fires when
the response body contains specific substrings ("do not have an
active Grok subscription", etc.). xAI has been seen to 403 standard
SuperGrok subscribers with a terser body that doesn't match those
keywords (#26847), and the recovery path would then mint a fresh
token, get a fresh 403, and loop until Ctrl+C.
Add a defense-in-depth check at the recovery call site: any 403 on
``provider == "xai-oauth"`` short-circuits ``try_refresh_current``
so the error surfaces immediately with the friendly hint from
``_summarize_api_error``. Keeps the existing keyword path for all
other providers untouched.
Tests:
* ``test_refresh_xai_oauth_pure_403_marked_tier_denied_not_relogin`` —
refresh-403 raises ``xai_oauth_tier_denied`` with
``relogin_required=False`` and the API-key fallback hint in body.
* ``test_format_auth_error_tier_denied_does_not_suggest_relogin`` —
the renderer does not append "Run ``hermes model``" for the new
code.
* ``test_recover_with_credential_pool_skips_refresh_on_bare_403_for_xai_oauth`` —
bare ``{"reason":"forbidden","message":"Forbidden"}`` body (which
does not match the existing keyword heuristic) still short-circuits
``try_refresh_current`` on xai-oauth.
Docs:
* Drop the "(any active tier)" claim from the xai-grok-oauth guide,
add a top-of-page warning callout, and a Troubleshooting section
for the 403-after-login case pointing at ``XAI_API_KEY`` +
``provider: xai`` as the documented fallback.
19 tasks
Contributor
🔎 Lint report:
|
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Salvage of #26855 (@xxxigm).
Summary
Makes the
xai-oauthprovider fail gracefully when xAI's backend returns HTTP 403 to a standard SuperGrok subscriber (subscription is active but the account is not on xAI's API-access allowlist). Stops the credential-pool refresh-loop and surfaces a useful error pointing at theXAI_API_KEYfallback.Changes
hermes_cli/auth.py: split 403 off from 400/401 inrefresh_xai_oauth_pureand_xai_oauth_exchange_code_for_tokens— newxai_oauth_tier_deniederror code withrelogin_required=False. Loopback-exchange port was adapted to the current_xai_oauth_exchange_code_for_tokenshelper shape (the PR's pre-refactor inline httpx.post block was already replaced by this helper on main).agent/agent_runtime_helpers.py(wasrun_agent.pyin the original PR — relocated to the refactoredrecover_with_credential_poolforwarder target): defense-in-depth — any 403 onxai-oauthis treated as entitlement to stop the refresh loop even when the body fails the existing_is_entitlement_failurekeyword set.website/docs/guides/xai-grok-oauth.md: drops the inaccurate "(any active tier)" claim, adds a Troubleshooting section for the HTTP 403 path with theXAI_API_KEYfallback recipe.Validation
scripts/run_tests.sh tests/hermes_cli/test_auth_xai_oauth_provider.py tests/run_agent/test_codex_xai_oauth_recovery.py -q→ 98/98 passing.scripts/run_tests.sh tests/agent/test_credential_pool.py -q→ 52/52 passing (no regression in the related auth/pool tests).Authorship preserved across 3 commits (cherry-pick + conflict resolution against current main).