Skip to content

Commit ad8889a

Browse files
committed
Treat caches as best-effort
1 parent 341c312 commit ad8889a

5 files changed

Lines changed: 88 additions & 16 deletions

File tree

scripts/benchmark-local.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function printRows(rows) {
127127
console.log('| --- | ---: | ---: | --- |');
128128
for (const row of rows) {
129129
const time = row.timedOut ? `>${row.wallSeconds.toFixed(2)}s` : `${row.wallSeconds.toFixed(2)}s`;
130-
const ram = row.maxRssKb == null ? 'n/a' : `${(row.maxRssKb / 1024 / 1024).toFixed(2)} GB`;
130+
const ram = row.maxRssKb == null ? 'n/a' : `${(row.maxRssKb / 1_000_000).toFixed(2)} GB`;
131131
const result = row.timedOut ? `timed out (${row.status})` : row.status === 0 ? 'complete' : `exit ${row.status}`;
132132
console.log(`| ${row.label} | ${time} | ${ram} | ${result} |`);
133133
}

src/engine.mjs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,24 @@ export async function collectUsage(options = {}) {
102102
const report = buildReport(aggregate, stats, nextFiles);
103103

104104
if (saveCache) {
105-
const saveResult = await saveCacheFile(cacheFile, {
106-
version: CACHE_VERSION,
107-
source: CACHE_SOURCE,
108-
timezone,
109-
billingThresholds,
110-
updatedAt: new Date().toISOString(),
111-
files: nextFiles,
112-
}, maxCacheBytes);
113-
report.stats.cacheBytes = saveResult.bytes;
114-
report.stats.cacheSaveSkippedBySize = !saveResult.saved;
115-
if (saveResult.saved) {
116-
report.stats.cacheFile = cacheFile;
117-
report.stats.cacheEntriesSaved = Object.keys(nextFiles).length;
105+
try {
106+
const saveResult = await saveCacheFile(cacheFile, {
107+
version: CACHE_VERSION,
108+
source: CACHE_SOURCE,
109+
timezone,
110+
billingThresholds,
111+
updatedAt: new Date().toISOString(),
112+
files: nextFiles,
113+
}, maxCacheBytes);
114+
report.stats.cacheBytes = saveResult.bytes;
115+
report.stats.cacheSaveSkippedBySize = !saveResult.saved;
116+
if (saveResult.saved) {
117+
report.stats.cacheFile = cacheFile;
118+
report.stats.cacheEntriesSaved = Object.keys(nextFiles).length;
119+
}
120+
} catch (error) {
121+
report.stats.cacheSaveSkippedByError = true;
122+
report.stats.cacheSaveError = error?.message ?? String(error);
118123
}
119124
} else {
120125
report.stats.cacheSaveSkippedBySize = false;
@@ -782,6 +787,8 @@ function createStats(cache, timezone, billingThresholds = DEFAULT_BILLING_THRESH
782787
bytesSkippedByTailCache: 0,
783788
cacheEntriesLoaded: Object.keys(cache.files).length,
784789
cacheEntriesSaved: 0,
790+
cacheSaveSkippedByError: false,
791+
cacheSaveError: null,
785792
discoveryMode: null,
786793
};
787794
}

src/pricing.mjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,25 @@ export async function loadPricingCatalog(options = {}) {
143143

144144
try {
145145
const fetched = await fetchLivePricing({ tier, timeoutMs: options.fetchTimeoutMs, priorityModels });
146-
await savePricingCache(cacheFile, fetched);
146+
let cacheState = cached ? 'refreshed' : 'created';
147+
let cacheSaveError = null;
148+
try {
149+
await savePricingCache(cacheFile, fetched);
150+
} catch (error) {
151+
cacheState = cached ? 'refreshed-cache-save-failed' : 'created-cache-save-failed';
152+
cacheSaveError = error?.message ?? String(error);
153+
}
147154
return createCatalog(fetched.data, {
148155
source: fetched.source,
149156
sourceUrl: fetched.sourceUrl,
150157
fetchedAt: fetched.fetchedAt,
151158
cacheFile,
152-
cacheState: cached ? 'refreshed' : 'created',
159+
cacheState,
153160
ttlHours: ttlMs / 3_600_000,
154161
modelCount: Object.keys(fetched.data).length,
155162
tier,
156163
priorityModels,
164+
cacheSaveError,
157165
});
158166
} catch (error) {
159167
const source = cachedForTier?.data ?? bundledPricingData(tier, { priorityModels });

test/engine.test.mjs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,38 @@ assert.equal(saveCap.stats.cacheSaveSkippedBySize, true);
249249
assert.equal(saveCap.stats.cacheEntriesSaved, 0);
250250
assert.equal(saveCap.totals.totalTokens, 7000);
251251

252+
const blockedCacheHome = path.join(root, 'blocked-cache-codex-home');
253+
const blockedCacheSessions = path.join(blockedCacheHome, 'sessions/2026/05/16');
254+
await mkdir(blockedCacheSessions, { recursive: true });
255+
await writeFile(
256+
path.join(blockedCacheSessions, 'blocked-cache.jsonl'),
257+
[
258+
JSON.stringify({ timestamp: '2026-05-16T00:00:00.000Z', type: 'turn_context', payload: { model: 'gpt-test' } }),
259+
JSON.stringify({
260+
timestamp: '2026-05-16T00:00:01.000Z',
261+
type: 'event_msg',
262+
payload: {
263+
type: 'token_count',
264+
info: { last_token_usage: { input_tokens: 3, cached_input_tokens: 1, output_tokens: 2, total_tokens: 5 } },
265+
},
266+
}),
267+
'',
268+
].join('\n'),
269+
);
270+
const cachePathBlocker = path.join(root, 'cache-path-blocker');
271+
await writeFile(cachePathBlocker, 'not a directory');
272+
const blockedCacheReport = await collectUsage({
273+
codexHome: blockedCacheHome,
274+
cacheFile: path.join(cachePathBlocker, 'index.json'),
275+
timezone: 'UTC',
276+
includePricing: false,
277+
});
278+
assert.equal(blockedCacheReport.totals.totalTokens, 5);
279+
assert.equal(blockedCacheReport.stats.cacheSaveSkippedByError, true);
280+
assert.match(blockedCacheReport.stats.cacheSaveError, /EEXIST|ENOTDIR|not a directory/i);
281+
assert.equal(blockedCacheReport.stats.cacheEntriesSaved, 0);
282+
assert.equal(blockedCacheReport.stats.cacheFile, null);
283+
252284
assert.throws(() => normalizeDate('2026-02-30'), /Invalid date/);
253285
assert.throws(() => normalizeDate('2026-99-99'), /Invalid date/);
254286

test/pricing.test.mjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,31 @@ try {
348348
globalThis.fetch = officialPriorityFetch;
349349
}
350350

351+
const cacheSaveFailureFetch = globalThis.fetch;
352+
const pricingCachePathBlocker = path.join(root, 'pricing-cache-path-blocker');
353+
await writeFile(pricingCachePathBlocker, 'not a directory');
354+
globalThis.fetch = async () => ({
355+
ok: true,
356+
text: async () => `
357+
<div component-export="TextTokenPricingTables" props="&quot;tier&quot;:[0,&quot;standard&quot;],[0,&quot;gpt-live-cache-fail&quot;],[0,9],[0,0.9],[0,18]"></div>
358+
`,
359+
});
360+
try {
361+
const cacheSaveFailureCatalog = await loadPricingCatalog({
362+
cacheFile: path.join(pricingCachePathBlocker, 'pricing.json'),
363+
ttlMs: 0,
364+
fetchTimeoutMs: 1000,
365+
});
366+
assert.equal(cacheSaveFailureCatalog.metadata.cacheState, 'created-cache-save-failed');
367+
assert.match(cacheSaveFailureCatalog.metadata.cacheSaveError, /EEXIST|ENOTDIR|not a directory/i);
368+
const livePriceAfterCacheSaveFailure = cacheSaveFailureCatalog.getPricing('gpt-live-cache-fail');
369+
assert.equal(livePriceAfterCacheSaveFailure.missing, false);
370+
assert.equal(livePriceAfterCacheSaveFailure.detail.source, 'openai-official');
371+
assert.equal(livePriceAfterCacheSaveFailure.price.inputCostPerMToken, 9);
372+
} finally {
373+
globalThis.fetch = cacheSaveFailureFetch;
374+
}
375+
351376
const tierFallbackCatalog = await loadPricingCatalog({
352377
tier: 'priority',
353378
pricingData: {

0 commit comments

Comments
 (0)