test(xai-oauth): pin tier-denied 403 behavior + docs warning for #26847
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.
This commit is contained in:
@@ -512,6 +512,56 @@ def test_recover_with_credential_pool_skips_refresh_on_entitlement_403():
|
||||
assert refresh_calls["n"] == 0, "try_refresh_current must NOT be called on entitlement 403"
|
||||
|
||||
|
||||
def test_recover_with_credential_pool_skips_refresh_on_bare_403_for_xai_oauth():
|
||||
"""A bare HTTP 403 from ``xai-oauth`` (no keyword match) must NOT loop refresh.
|
||||
|
||||
Regression for #26847 — xAI's backend has been seen to 403 standard
|
||||
SuperGrok subscribers with a terser body that doesn't contain any of
|
||||
the existing entitlement keywords ("do not have an active Grok
|
||||
subscription", etc.). Before the defense-in-depth guard, the recovery
|
||||
path would happily mint a fresh token, get a fresh 403, and spin.
|
||||
"""
|
||||
from run_agent import AIAgent
|
||||
from agent.error_classifier import FailoverReason
|
||||
|
||||
agent = _make_codex_agent()
|
||||
assert agent.provider == "xai-oauth"
|
||||
|
||||
refresh_calls = {"n": 0}
|
||||
|
||||
class _FakePool:
|
||||
def try_refresh_current(self):
|
||||
refresh_calls["n"] += 1
|
||||
return MagicMock(id="should_not_be_called")
|
||||
|
||||
def mark_exhausted_and_rotate(self, **_kwargs):
|
||||
return None
|
||||
|
||||
def has_available(self):
|
||||
return False
|
||||
|
||||
agent._credential_pool = _FakePool()
|
||||
|
||||
error_context = {
|
||||
"reason": "forbidden",
|
||||
"message": "Forbidden",
|
||||
}
|
||||
assert not AIAgent._is_entitlement_failure(error_context, 403), (
|
||||
"Pre-condition: bare 'Forbidden' body must NOT match the keyword "
|
||||
"heuristic — otherwise this test isn't covering the defense-in-depth path."
|
||||
)
|
||||
|
||||
recovered, _retried_429 = agent._recover_with_credential_pool(
|
||||
status_code=403,
|
||||
has_retried_429=False,
|
||||
classified_reason=FailoverReason.auth,
|
||||
error_context=error_context,
|
||||
)
|
||||
|
||||
assert recovered is False, "Bare 403 on xai-oauth must surface, not refresh-loop"
|
||||
assert refresh_calls["n"] == 0, "try_refresh_current must NOT be called on xai-oauth 403"
|
||||
|
||||
|
||||
def test_recover_with_credential_pool_still_refreshes_genuine_auth_failure():
|
||||
"""Regression guard: legitimate auth errors must still trigger refresh."""
|
||||
from run_agent import AIAgent
|
||||
|
||||
Reference in New Issue
Block a user