fix: detect org-access-denied errors, suppress chat output, and rotate Claude tokens
When a Claude account is suspended, the API returns "Your organization does not have access to Claude" on the success path or "Failed to authenticate. API Error: 403 terminated" on the error path. Both are now classified as 'org-access-denied', suppressed from Discord output, and trigger automatic token rotation. If all tokens are exhausted, enters cooldown without Kimi fallback (same as usage-exhausted and auth-expired). Changes: - agent-error-detection: add isClaudeOrgAccessDeniedMessage(), expand classifyClaudeAuthError() and shouldRotateClaudeToken() - streamed-output-evaluator: detect org-access-denied in success-path chain - provider-fallback: add NO_FALLBACK_COOLDOWN_REASONS set and isPrimaryNoFallbackCooldownActive() helper - provider-retry: handle org-access-denied in rotation loop - message-agent-executor / task-scheduler: use generalized no-fallback cooldown check - Tests: +8 test cases across 5 test files (415 total passing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ export type RotationOutcome =
|
||||
| { type: 'success' }
|
||||
| { type: 'error'; message?: string }
|
||||
| { type: 'needs-fallback'; trigger: TriggerInfo }
|
||||
| { type: 'no-fallback'; trigger: TriggerInfo }; // usage-exhausted/auth-expired
|
||||
| { type: 'no-fallback'; trigger: TriggerInfo }; // usage-exhausted/auth-expired/org-access-denied
|
||||
|
||||
// ── Shared rotation loop ─────────────────────────────────────────
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function runClaudeRotationLoop(
|
||||
) {
|
||||
logger.info(
|
||||
{ ...logContext, reason: trigger.reason },
|
||||
'Claude rate-limited, retrying with rotated account',
|
||||
'Claude account unavailable, retrying with rotated account',
|
||||
);
|
||||
|
||||
const attempt = await runAttempt();
|
||||
@@ -162,10 +162,11 @@ export async function runClaudeRotationLoop(
|
||||
|
||||
// ── All tokens exhausted ──
|
||||
|
||||
// Usage exhausted or auth-expired: don't fall back to Kimi
|
||||
// Usage/auth/org access failures: don't fall back to Kimi
|
||||
if (
|
||||
trigger.reason === 'usage-exhausted' ||
|
||||
trigger.reason === 'auth-expired'
|
||||
trigger.reason === 'auth-expired' ||
|
||||
trigger.reason === 'org-access-denied'
|
||||
) {
|
||||
markPrimaryCooldown(trigger.reason, trigger.retryAfterMs);
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user