fix: recover Codex rotation auth failures

- mark Codex bearer/refresh failures as terminal auth-expired states

- sync refreshed session auth back to rotation slots and revive refreshed dead_auth slots

- stop paired arbiter retry loops when Codex accounts are unavailable

- add regression coverage for rotation leases, follow-up suppression, and arbiter closure
This commit is contained in:
ejclaw
2026-06-02 00:21:16 +09:00
parent 1ff6b3434f
commit 03d4c81192
21 changed files with 1082 additions and 52 deletions

View File

@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
import {
classifyAgentError,
classifyClaudeAuthError,
classifyCodexAuthError,
detectClaudeProviderFailureMessage,
isClaudeOrgAccessDeniedMessage,
shouldRotateClaudeToken,
@@ -66,6 +67,32 @@ describe('agent-error-detection', () => {
});
});
it('classifies Codex reused refresh-token errors as auth-expired', () => {
expect(
classifyCodexAuthError(
'Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.',
),
).toEqual({
category: 'auth-expired',
reason: 'auth-expired',
});
});
it('classifies a Codex auth-expired trigger reason as auth-expired', () => {
expect(classifyCodexAuthError('auth-expired')).toEqual({
category: 'auth-expired',
reason: 'auth-expired',
});
});
it('classifies Codex workspace credit exhaustion as rate-limit', () => {
expect(classifyAgentError('Workspace out of credits')).toEqual({
category: 'rate-limit',
reason: '429',
retryAfterMs: undefined,
});
});
it('marks only Claude quota/auth reasons as Claude rotation reasons', () => {
expect(shouldRotateClaudeToken('429')).toBe(true);
expect(shouldRotateClaudeToken('usage-exhausted')).toBe(true);