From 9031e5a085e8c1b4b58e4e871e4d98dbd48bcfc3 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 17 Feb 2026 20:51:47 +0700 Subject: [PATCH] fix(cliproxy): log unknown codex quota window labels - detect and de-duplicate unclassified quota window labels - emit diagnostics in verbose/debug modes for API label drift - add unit tests for unknown-label extraction behavior --- src/cliproxy/quota-fetcher-codex.ts | 23 +++++++- .../unit/cliproxy/quota-fetcher-codex.test.ts | 52 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/cliproxy/quota-fetcher-codex.ts b/src/cliproxy/quota-fetcher-codex.ts index 3c860d5b..bc83f178 100644 --- a/src/cliproxy/quota-fetcher-codex.ts +++ b/src/cliproxy/quota-fetcher-codex.ts @@ -82,6 +82,20 @@ function getCodexWindowKind(label: string): CodexWindowKind { return 'unknown'; } +function getUnknownCodexWindowLabels(windows: CodexQuotaWindow[]): string[] { + const unknownLabels = windows + .filter((window) => getCodexWindowKind(window.label) === 'unknown') + .map((window) => window.label) + .filter((label): label is string => typeof label === 'string' && label.trim().length > 0); + return Array.from(new Set(unknownLabels)); +} + +function shouldLogCodexWindowWarnings(verbose: boolean): boolean { + if (verbose) return true; + const debugFlag = process.env['CCS_DEBUG']; + return debugFlag === '1' || debugFlag === 'true'; +} + /** * Build explicit 5h + weekly usage summary from raw Codex windows. * Falls back to shortest/longest reset windows if API labels change. @@ -388,6 +402,13 @@ export async function fetchCodexQuota( const data = (await response.json()) as CodexUsageResponse; const windows = buildCodexQuotaWindows(data); + const unknownWindowLabels = getUnknownCodexWindowLabels(windows); + if (unknownWindowLabels.length > 0 && shouldLogCodexWindowWarnings(verbose)) { + console.error( + `[!] Codex quota detected unknown window labels: ${unknownWindowLabels.join(', ')}` + ); + console.error(' Window classification may need an update for upstream API changes.'); + } const coreUsage = buildCodexCoreUsageSummary(windows); // Extract plan type @@ -475,4 +496,4 @@ export async function fetchAllCodexQuotas( } // Export for testing -export { readCodexAuthData, buildCodexQuotaWindows }; +export { readCodexAuthData, buildCodexQuotaWindows, getUnknownCodexWindowLabels }; diff --git a/tests/unit/cliproxy/quota-fetcher-codex.test.ts b/tests/unit/cliproxy/quota-fetcher-codex.test.ts index b771ee4d..69b3f21a 100644 --- a/tests/unit/cliproxy/quota-fetcher-codex.test.ts +++ b/tests/unit/cliproxy/quota-fetcher-codex.test.ts @@ -8,6 +8,7 @@ import { describe, it, expect } from 'bun:test'; import { buildCodexQuotaWindows, buildCodexCoreUsageSummary, + getUnknownCodexWindowLabels, } from '../../../src/cliproxy/quota-fetcher-codex'; describe('Codex Quota Fetcher', () => { @@ -255,4 +256,55 @@ describe('Codex Quota Fetcher', () => { expect(summary.weekly).toBeNull(); }); }); + + describe('getUnknownCodexWindowLabels', () => { + it('returns unknown labels and de-duplicates them', () => { + const labels = getUnknownCodexWindowLabels([ + { + label: 'Window A', + usedPercent: 1, + remainingPercent: 99, + resetAfterSeconds: 10, + resetAt: null, + }, + { + label: 'Window A', + usedPercent: 2, + remainingPercent: 98, + resetAfterSeconds: 20, + resetAt: null, + }, + { + label: 'Primary', + usedPercent: 3, + remainingPercent: 97, + resetAfterSeconds: 30, + resetAt: null, + }, + ]); + + expect(labels).toEqual(['Window A']); + }); + + it('returns empty array when all labels are recognized', () => { + const labels = getUnknownCodexWindowLabels([ + { + label: 'Primary', + usedPercent: 10, + remainingPercent: 90, + resetAfterSeconds: 100, + resetAt: null, + }, + { + label: 'Code Review (Secondary)', + usedPercent: 20, + remainingPercent: 80, + resetAfterSeconds: 200, + resetAt: null, + }, + ]); + + expect(labels).toEqual([]); + }); + }); });