[Bug]: claude-sdk-oauth auth failure is stored as blockReason: rate_limit — all accounts blocked while quota is fine, expired blocks never clear

Author: ayalcohCreated Sep 16, 2026Updated Sep 17, 2026

Summary

claude-sdk-oauth reports all accounts blocked (rate limit) and refuses to run, while the Claude subscription is not rate limited at all. claude /usage in Claude Code shows only Fable at its weekly limit this week; Claude models are well under quota and requests succeed there.

The block appears to be an authentication failure misclassified as a rate limit, persisted into the credential-pool sidecar with a fabricated "soonest automatic retry" time. Because it is stamped as rate_limit, the user is told to wait for a reset that will never fix it, and the correct action (re-login) is not the advertised one.

Error as shown in the TUI

Error: All Claude accounts for claude-sdk-oauth are currently blocked (rate limit or auth errors).
  Soonest automatic retry: 2026-09-16T08:46:07.773Z.
  /claude-account list  - inspect account states
  /login claude-sdk-oauth  - add another account

Session continuity lost - resent the full conversation (query_failed)

That block repeats five times, each repetition resending the whole conversation, and then ends with:

Error: Retry failed after 5 attempts: Retry cancelled

Evidence from local state

~/.omo/agent/credential-pool-state.json right now (read at 2026-09-16T08:50:05Z):

pool.providers.claude-sdk-oauth.lanes.stored.slots.imported-anthropic.blockReason  = rate_limit
pool.providers.claude-sdk-oauth.lanes.stored.slots.imported-anthropic.blockedUntil = 2026-09-16T04:56:51.287Z
pool.providers.claude-sdk-oauth.lanes.stored.slots.imported-anthropic.failureCount = 1
pool.providers.claude-sdk-oauth.lanes.stored.slots.Main.blockReason  = rate_limit
pool.providers.claude-sdk-oauth.lanes.stored.slots.Main.blockedUntil = 2026-09-16T05:03:23.746Z
pool.providers.claude-sdk-oauth.lanes.stored.slots.Main.failureCount = 2

Two things stand out:

  1. blockReason: rate_limit is wrong. The Claude account is not rate limited; only Fable is at its weekly cap.
  2. Both blockedUntil values are ~4 hours in the past yet the slots are still recorded as blocked. Expired blocks are not being cleared from the sidecar.

~/.omo/agent/auth.json currently carries no blockReason for these slots, so the two health stores disagree: the auth store considers the slots fine while the pool sidecar still holds stale rate_limit blocks.

Suspected mechanism

Traced earlier the same day on this machine: the inner SDK failure was a permanent auth error, not a quota error —

SDK-ts authentication_failed, HTTP 401
Failed to authenticate. API Error: 401 OAuth access token has been revoked.

That inner error is surfaced as the generic guidance string "... (rate limit or auth errors)". The outer credential-pool classifier then appears to string-match "rate limit" in that generic message and persists blockReason: rate_limit with a timed blockedUntil. A permanent auth failure is thereby laundered into a temporary quota block.

That also explains the recurrence pattern: clearing the sidecar alone restores service briefly, then the next request re-stamps a rate_limit block, because the underlying credential problem was never surfaced to the user as an auth problem.

Expected

  1. An auth/401 failure is classified as auth_error, never as rate_limit, and the message tells the user to re-authenticate instead of quoting a retry time.
  2. The classifier keys off the structured inner error, not off substrings of its own generic guidance text.
  3. A blockedUntil in the past is treated as expired and cleared, rather than leaving the slot blocked.
  4. A non-retryable auth failure is not retried 5 times with a full conversation resend each round.
  5. The two health stores (auth.json block fields and credential-pool-state.json sidecar) agree, or one is authoritative.

Actual

Correct Claude quota, revoked/failed credential, reported as a rate limit with a retry deadline that does not resolve it; stale expired blocks persist; five full-conversation resends burn cache and tokens before the turn dies.

Environment

  • omo-ai 5.0.0-0.beta.65 (engine: senpi 2026.9.16)
  • macOS (Darwin 25.5.0), arm64 (Apple M4), Node v24.15.0
  • Provider claude-sdk-oauth, two stored slots (imported-anthropic, Main) which are the same Claude account, not independent quota

Related

Related to #7084 (login appends slots; auth_error never expires), but distinct: this report is about an auth failure being labelled and stored as rate_limit, the stale expired blockedUntil, and the 5x retry/resend loop on a non-retryable error.

Source: code-yeongyu/oh-my-openagent