fix: refresh session-store cache when file size changes within same mtime tick

The session-store cache used only mtime for invalidation. In fast CI
runs (especially under bun), test writes to the session store can
complete within the same filesystem mtime granularity (~1s on HFS+/ext4),
so the cache returns stale data. This caused non-deterministic failures
in model precedence tests where a session override written to disk was
not observed by the next loadSessionStore() call.

Fix: add file size as a secondary cache invalidation signal. The cache
now checks both mtimeMs and sizeBytes — if either differs from the
cached values, it reloads from disk.

Changes:
- cache-utils.ts: add getFileSizeBytes() helper
- sessions/store.ts: extend SessionStoreCacheEntry with sizeBytes field,
  check size in cache-hit path, populate size on cache writes
- sessions.cache.test.ts: add regression test for same-mtime rewrite
This commit is contained in:
Josh Lehman
2026-03-02 13:33:08 -08:00
committed by Peter Steinberger
parent f9025c3f55
commit 1212328c8d
3 changed files with 53 additions and 2 deletions

View File

@@ -17,7 +17,12 @@ import {
normalizeSessionDeliveryFields,
type DeliveryContext,
} from "../../utils/delivery-context.js";
import { getFileMtimeMs, isCacheEnabled, resolveCacheTtlMs } from "../cache-utils.js";
import {
getFileMtimeMs,
getFileSizeBytes,
isCacheEnabled,
resolveCacheTtlMs,
} from "../cache-utils.js";
import { loadConfig } from "../config.js";
import type { SessionMaintenanceConfig, SessionMaintenanceMode } from "../types.base.js";
import { enforceSessionDiskBudget, type SessionDiskBudgetSweepResult } from "./disk-budget.js";
@@ -39,6 +44,7 @@ type SessionStoreCacheEntry = {
loadedAt: number;
storePath: string;
mtimeMs?: number;
sizeBytes?: number;
serialized?: string;
};
@@ -208,7 +214,8 @@ export function loadSessionStore(
const cached = SESSION_STORE_CACHE.get(storePath);
if (cached && isSessionStoreCacheValid(cached)) {
const currentMtimeMs = getFileMtimeMs(storePath);
if (currentMtimeMs === cached.mtimeMs) {
const currentSizeBytes = getFileSizeBytes(storePath);
if (currentMtimeMs === cached.mtimeMs && currentSizeBytes === cached.sizeBytes) {
// Return a deep copy to prevent external mutations affecting cache
return structuredClone(cached.store);
}
@@ -288,6 +295,7 @@ export function loadSessionStore(
loadedAt: Date.now(),
storePath,
mtimeMs,
sizeBytes: getFileSizeBytes(storePath),
serialized: serializedFromDisk,
});
}
@@ -667,6 +675,7 @@ function updateSessionStoreWriteCaches(params: {
loadedAt: Date.now(),
storePath: params.storePath,
mtimeMs,
sizeBytes: getFileSizeBytes(params.storePath),
serialized: params.serialized,
});
}