Files
OmniRoute/tests/unit
Ravi Tharuma 1683d57749 fix(oauth): keep the Claude refresh token so the retry budget is reachable
An unrecoverable Claude OAuth refresh (invalid_grant / refresh_token_reused,
often a dual-consumer race on the same Claude Max account) left the connection
sticky-dead: active, expired and unable to recover without a manual re-auth.

#11414 already keeps such a connection retryable for EXPIRED_RETRY_MAX sweeps,
but the very same update ran `refreshToken: null` for every rotating provider,
Claude included. The next sweep then stops at the `!conn.refreshToken` guard,
whose self-heal branch only fires while testStatus is empty or "active" — the
row is already "expired", so checkConnection returns silently and the retry
budget is never spent. The #11414 regression test missed this because it drives
a synthetic provider that is not in ROTATING_REFRESH_PROVIDERS.

- Claude opts out of clearing the rotating refresh token on the unrecoverable
  path, for the same reason #3679 preserves it for non-rotating providers: it is
  the user's only recovery artifact. Codex and the other rotating providers keep
  clearing their genuinely consumed one-time-use tokens.
- CredentialHealth's sweep now honors the refresh circuit and parks the next
  attempt on the circuit deadline instead of re-probing a connection whose token
  refresh is already backing off.
- isInRefreshBackoff moves to src/lib/tokenRefreshCircuit.ts so CredentialHealth
  can use it without importing tokenHealthCheck's auto-starting scheduler;
  tokenHealthCheck re-exports it for existing callers.

Regression test drives the real `claude` provider through two consecutive
sweeps: without the fix the token is gone after the first and the second sweep
never spends retry 2. A Codex case guards the unchanged behavior.

Closes #13183
2026-09-10 13:55:14 -03:00
..
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00
2026-08-26 14:25:01 -03:00