fix(recent-projects): skip oversized codex session cache
This commit is contained in:
parent
8c86def84d
commit
031e5eda2f
2 changed files with 66 additions and 0 deletions
|
|
@ -30,6 +30,7 @@ const CODEX_SESSION_FILE_CACHE_RELATIVE_PATH = path.join(
|
|||
'recent-projects',
|
||||
'codex-session-files-index.json'
|
||||
);
|
||||
const CODEX_SESSION_FILE_CACHE_MAX_BYTES = 4 * 1024 * 1024;
|
||||
|
||||
interface CodexSessionFileEntry {
|
||||
filePath: string;
|
||||
|
|
@ -663,6 +664,16 @@ export class CodexSessionFileRecentProjectsSourceAdapter implements RecentProjec
|
|||
|
||||
async #readCacheSafe(): Promise<CodexSessionFileCacheFile> {
|
||||
try {
|
||||
const stats = await fs.stat(this.#cachePath);
|
||||
if (stats.size > CODEX_SESSION_FILE_CACHE_MAX_BYTES) {
|
||||
this.deps.logger.warn('codex session-file recent-projects cache skipped - too large', {
|
||||
cachePath: this.#cachePath,
|
||||
bytes: stats.size,
|
||||
maxBytes: CODEX_SESSION_FILE_CACHE_MAX_BYTES,
|
||||
});
|
||||
return emptyCache();
|
||||
}
|
||||
|
||||
const raw = await fs.readFile(this.#cachePath, 'utf8');
|
||||
const parsed = JSON.parse(raw) as Partial<CodexSessionFileCacheFile>;
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -596,6 +596,61 @@ describe('CodexSessionFileRecentProjectsSourceAdapter', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('skips an oversized legacy session-file cache before reading it', async () => {
|
||||
const codexHome = path.join(tempDir, '.codex');
|
||||
const appDataPath = path.join(tempDir, 'app-data');
|
||||
const logger = createLogger();
|
||||
const identityResolver = {
|
||||
resolve: vi.fn().mockResolvedValue(null),
|
||||
} as unknown as RecentProjectIdentityResolver;
|
||||
await writeRollout(
|
||||
path.join(codexHome, 'sessions', '2026', '04', '14', 'rollout-alpha.jsonl'),
|
||||
{
|
||||
cwd: '/Users/test/projects/alpha',
|
||||
},
|
||||
new Date('2026-04-14T12:00:00.000Z')
|
||||
);
|
||||
const cachePath = getSessionFileCachePath(appDataPath);
|
||||
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
||||
await fs.writeFile(cachePath, 'x', 'utf8');
|
||||
await fs.truncate(cachePath, 4 * 1024 * 1024 + 1);
|
||||
const readFileSpy = vi.spyOn(fs, 'readFile');
|
||||
|
||||
const adapter = new CodexSessionFileRecentProjectsSourceAdapter({
|
||||
getActiveContext: () => ({ type: 'local', id: 'local-1' }) as never,
|
||||
getLocalContext: () => ({ type: 'local', id: 'local-1' }) as never,
|
||||
identityResolver,
|
||||
logger,
|
||||
codexHome,
|
||||
appDataPath,
|
||||
});
|
||||
|
||||
const result = await adapter.list();
|
||||
|
||||
expect(readFileSpy).not.toHaveBeenCalledWith(cachePath, 'utf8');
|
||||
expect(result).toEqual({
|
||||
candidates: [
|
||||
expect.objectContaining({
|
||||
primaryPath: '/Users/test/projects/alpha',
|
||||
}),
|
||||
],
|
||||
degraded: false,
|
||||
});
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'codex session-file recent-projects cache skipped - too large',
|
||||
expect.objectContaining({
|
||||
bytes: 4 * 1024 * 1024 + 1,
|
||||
maxBytes: 4 * 1024 * 1024,
|
||||
})
|
||||
);
|
||||
await expect(fs.stat(cachePath)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
size: expect.any(Number),
|
||||
})
|
||||
);
|
||||
expect((await fs.stat(cachePath)).size).toBeLessThan(4 * 1024 * 1024);
|
||||
});
|
||||
|
||||
it('returns a degraded partial result under the uncached read cap and completes on the next cached pass', async () => {
|
||||
const codexHome = path.join(tempDir, '.codex');
|
||||
const appDataPath = path.join(tempDir, 'app-data');
|
||||
|
|
|
|||
Loading…
Reference in a new issue