Claude Code v2.1.78 changed metadata.user_id from a template literal
(`user_${id}_account_${uuid}_session_${sid}`) to a JSON-encoded object
(`JSON.stringify({device_id, account_uuid, session_id})`), breaking
session ID extraction via `_session_` substring match.
- Fix extractCCMSessionID to try JSON parse first, fallback to legacy
- Remove subscriptionType/rateLimitTier/isMax from oauthCredentials
(profile state does not belong in auth credentials)
- Add state_path option for persisting profile state across restarts
- Parse account.uuid from /api/oauth/profile response
- Inject account_uuid into forwarded requests when client sends it empty
(happens when using ANTHROPIC_AUTH_TOKEN instead of Claude AI OAuth)
External credentials returning 400 are marked unavailable for pollInterval
duration; status stream/poll success clears the rejection early. Default
credentials trigger a stale poll to let the usage API detect account issues
without causing 429 storms.
pollUsage(ctx) accepted caller context, and service_status.go passed
r.Context() which gets canceled on client disconnect or service shutdown.
This caused incrementPollFailures → interruptConnections on transient
cancellations. Each implementation now uses its own persistent context:
defaultCredential uses serviceContext, externalCredential uses
getReverseContext().
- Add fiveHourReset/weeklyReset to statusPayload and aggregatedStatus
with weight-averaged reset time aggregation across credential pools
- Rewrite response headers (utilization + reset times) for all users,
not just external credential users
- Rewrite WebSocket rate_limits events for all users with aggregated values
- Add proactive WebSocket status push: synthetic codex.rate_limits events
sent on connection start and on status changes via statusObserver
- Remove one-shot stream forward compatibility (statusStreamHeader,
restoreLastUpdatedIfUnchanged, oneShot detection)
- Remove unused onBecameUnusable field from CCM credential structs
(OCM wires it for WebSocket interruption; CCM has no equivalent)
- Replace time.After with time.NewTimer in doHTTPWithRetry and
connectorLoop to avoid timer leaks on context cancellation
- Pass already-resolved provider to rewriteResponseHeadersForExternalUser
instead of re-resolving via credentialForUser
- Hoist reverseYamuxConfig to package-level var (immutable, no need to
allocate on every call)
The usage API itself has rate limits. A 429 from it means "poll less
frequently", not that the account exceeded its usage quota. Previously
incrementPollFailures() was called, marking the credential unusable and
interrupting in-flight connections.
Now: parse Retry-After, store as usageAPIRetryDelay, and retry after
that delay. The credential stays usable and relies on passive header
updates for usage data in the meantime.
Scale remaining capacity by plan weight (Pro=1, Max 5x=5, Max 20x=10
for CCM; Plus=1, Pro=10 for OCM) so higher-tier accounts contribute
proportionally more. Factor in weekly reset proximity so credentials
about to reset are preferred ("use it or lose it").
Auto-detect plan weight from subscriptionType + rateLimitTier (CCM)
or plan_type (OCM). Fetch /api/oauth/profile when rateLimitTier is
missing from the credential file. External credentials accept a
manual plan_weight option.
Refuse to refresh tokens when the credential file is not writable,
preventing server-side invalidation of the old refresh token that
would make the credential permanently unusable after restart.