fix: usage API rate-limit guard and cleanup debug logging

This commit is contained in:
Eyejoker
2026-03-24 03:47:46 +09:00
parent 6a73421d4f
commit f6ba879e8a
4 changed files with 37 additions and 34 deletions

View File

@@ -58,13 +58,17 @@ function loadUsageDiskCache(): void {
if (fs.existsSync(USAGE_CACHE_FILE)) {
usageDiskCache = JSON.parse(fs.readFileSync(USAGE_CACHE_FILE, 'utf-8'));
}
} catch { /* start fresh */ }
} catch {
/* start fresh */
}
}
function saveUsageDiskCache(): void {
try {
fs.writeFileSync(USAGE_CACHE_FILE, JSON.stringify(usageDiskCache));
} catch { /* best effort */ }
} catch {
/* best effort */
}
}
function cacheKey(token: string): string {
@@ -104,7 +108,9 @@ async function fetchUsageForToken(
return null;
}
if (res.status === 429) {
logger.warn('Claude usage API: rate limited (429), returning cached data');
logger.warn(
'Claude usage API: rate limited (429), returning cached data',
);
return usageDiskCache[cacheKey(token)]?.usage ?? null;
}
if (!res.ok) {

View File

@@ -48,15 +48,13 @@ interface FallbackConfig {
// ── State ────────────────────────────────────────────────────────
let cooldown: CooldownState | null = null;
let lastUsageAvailabilityCheck:
| {
checkedAt: number;
result: 'available' | 'exhausted' | 'unknown';
}
| null = null;
let usageAvailabilityCheckPromise:
| Promise<'available' | 'exhausted' | 'unknown'>
| null = null;
let lastUsageAvailabilityCheck: {
checkedAt: number;
result: 'available' | 'exhausted' | 'unknown';
} | null = null;
let usageAvailabilityCheckPromise: Promise<
'available' | 'exhausted' | 'unknown'
> | null = null;
const USAGE_RECOVERY_RECHECK_MS = 30_000;
@@ -213,7 +211,10 @@ export async function getActiveProvider(): Promise<string> {
}
if (usageAvailability === 'exhausted') {
// Current token exhausted — try rotating to another token (ignore cooldowns)
if (getTokenCount() > 1 && rotateToken(undefined, { ignoreRateLimits: true })) {
if (
getTokenCount() > 1 &&
rotateToken(undefined, { ignoreRateLimits: true })
) {
logger.info(
'Claude current token exhausted, rotated to next token — retrying',
);

View File

@@ -307,14 +307,14 @@ Check the run.
phase: 'intermediate',
result: "You're out of extra usage · resets 4am (Asia/Seoul)",
});
await onOutput?.({
status: 'success',
result: "You're out of extra usage · resets 4am (Asia/Seoul)",
});
return {
status: 'success',
result: null,
};
await onOutput?.({
status: 'success',
result: "You're out of extra usage · resets 4am (Asia/Seoul)",
});
return {
status: 'success',
result: null,
};
},
)
.mockImplementationOnce(
@@ -324,14 +324,14 @@ Check the run.
_onProcess: unknown,
onOutput?: (output: Record<string, unknown>) => Promise<void>,
) => {
await onOutput?.({
status: 'success',
result: 'rotated scheduled task response',
});
return {
status: 'success',
result: null,
};
await onOutput?.({
status: 'success',
result: 'rotated scheduled task response',
});
return {
status: 'success',
result: null,
};
},
);

View File

@@ -156,11 +156,7 @@ export function rotateToken(
for (let i = 1; i < tokens.length; i++) {
const idx = (currentIndex + i) % tokens.length;
const state = tokens[idx];
if (
ignoreRL ||
!state.rateLimitedUntil ||
state.rateLimitedUntil <= now
) {
if (ignoreRL || !state.rateLimitedUntil || state.rateLimitedUntil <= now) {
state.rateLimitedUntil = null;
currentIndex = idx;
logger.info(