Skip to content

Codex session cache write is not atomic — a torn write loses the whole cache #689

Description

@chhhee10

writeCacheEntry() in lib/codex-sessions.ts does a read-modify-write with no atomic rename:

function writeCacheEntry(sessionId: string, path: string): void {
  try {
    mkdirSync(dirname(CACHE_PATH), { recursive: true });
    const cache = readCache();
    cache[sessionId] = path;
    writeFileSync(CACHE_PATH, JSON.stringify(cache), "utf-8");   // not atomic
  } catch {
    // Cache is best-effort
  }
}

The dashboard fans out Codex session lookups in parallel on the projects page, so these writes can interleave.

Why it matters

A lost entry isn't the problem — that's just a cache miss, and the next lookup rescans and repopulates it.

The problem is a torn file. If a write is interrupted partway, readCache() hits invalid JSON, throws, and the entire cache map is lost — not one entry. That turns a harmless race into a full cache wipe.

Where to fix

lib/codex-sessions.ts:

  1. Write to a process-unique temp path — ${CACHE_PATH}.${process.pid}.tmp
  2. renameSync() it into place (atomic on the same filesystem)
  3. On failure, unlink the temp file so nothing is left behind

Keep the whole thing best-effort: the cache is an optimisation and must never throw into the caller.

Done when

  • a test proves the cache file is written correctly and no .tmp files survive
  • existing lib/codex-sessions tests still pass

_getCacheFilePath() is already exported from that module for exactly this kind of test.

Small and self-contained. Supersedes #276, which was closed while the fix was still unmerged, so it never landed.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions