Skip to content

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

Merged
steipete merged 4 commits intomainfrom
fix/session-store-cache-size-invalidation
Mar 2, 2026
Merged

fix: refresh session-store cache when file size changes within same mtime tick#32191
steipete merged 4 commits intomainfrom
fix/session-store-cache-size-invalidation

Conversation

@jalehman
Copy link
Contributor

@jalehman jalehman commented Mar 2, 2026

Problem

The session-store cache used only mtimeMs 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 (isolated-agent.uses-last-non-empty-agent-text-as.test.ts) where a session override written to disk was not picked up by the next loadSessionStore() call — the default anthropic provider was returned instead of the expected openai override.

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

  • src/config/cache-utils.ts: add getFileSizeBytes() helper
  • src/config/sessions/store.ts: extend SessionStoreCacheEntry with sizeBytes field, check size in cache-hit path, populate size on cache writes
  • src/config/sessions.cache.test.ts: add regression test for same-mtime rewrite scenario

Testing

All 9 session cache tests pass locally (8 existing + 1 new regression test). The flaky model precedence test passes consistently with this fix.

@openclaw-barnacle openclaw-barnacle bot added size: S maintainer Maintainer-authored PR labels Mar 2, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6ac71cbdc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR improves the session-store cache invalidation by adding file size as a secondary signal alongside mtimeMs, fixing a real flakiness issue in CI where writes within the same mtime granularity window returned stale cached data.

  • src/config/cache-utils.ts: New getFileSizeBytes helper added correctly, mirroring getFileMtimeMs.
  • src/config/sessions/store.ts: sizeBytes field added to SessionStoreCacheEntry and checked in both the cache-hit path and both write-through cache update sites — logic is correct.
  • src/config/sessions.cache.test.ts: The new regression test has a subtle issue — fs.statSync is called after fs.writeFileSync, so utimesSync resets mtime to the newly-written file's mtime rather than the originally-cached mtime. On sub-second-precision filesystems (APFS, modern ext4, etc.) this means the mtime will already differ between the two writes, the cache invalidates on mtime alone, and the sizeBytes code path is never actually exercised by the test. The stat capture should happen before writing store2 to reliably reproduce the same-mtime scenario.

Confidence Score: 4/5

  • The production fix in store.ts and cache-utils.ts is correct and safe to merge; the regression test needs a small fix to reliably exercise the new code path.
  • The core logic change is straightforward and low-risk — adding a secondary cache invalidation signal. The only concern is the regression test, which may not reliably cover the intended scenario on high-precision filesystems due to reading stat after rather than before the file write. This doesn't affect production behaviour, only the quality of the test as a regression guard.
  • src/config/sessions.cache.test.ts — the utimesSync call should use a stat captured before writing store2 to reliably force same-mtime.

Last reviewed commit: e6ac71c

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@jalehman jalehman force-pushed the fix/session-store-cache-size-invalidation branch from e6ac71c to 4fc8ebd Compare March 2, 2026 21:41
jalehman and others added 4 commits March 2, 2026 22:07
…time 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
@steipete steipete force-pushed the fix/session-store-cache-size-invalidation branch from c9c209c to 3b7411c Compare March 2, 2026 22:09
@steipete steipete merged commit 11dcf96 into main Mar 2, 2026
3 checks passed
@steipete steipete deleted the fix/session-store-cache-size-invalidation branch March 2, 2026 22:09
@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Landed via temp rebase onto main.

  • Gate: pnpm test src/config/sessions.cache.test.ts
  • Land commit: 3b7411c
  • Merge commit: 11dcf96

Thanks @jalehman!

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b7411c9e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fs.writeFileSync(storePath, json2);

// Force mtime to match the cached value so only size differs
fs.utimesSync(storePath, preWriteStat.atime, preWriteStat.mtime);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve precise mtime when forcing same-tick rewrite

fs.utimesSync(storePath, preWriteStat.atime, preWriteStat.mtime) uses Date values, which can shift away from the original mtimeMs precision on filesystems with sub-millisecond timestamps (fresh evidence: on this branch, a Date-based utimes changed mtimeMs for the same file). When that happens, this regression test reloads due to mtime mismatch instead of size mismatch, so it can still pass even if size-based invalidation regresses; set mtime from the original high-precision timestamp (mtimeMs) so only size differences trigger the reload.

Useful? React with 👍 / 👎.

@aisle-research-bot
Copy link

aisle-research-bot bot commented Mar 2, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low Static code scan cache can be bypassed by rewriting a file while preserving mtime and size

1. 🔵 Static code scan cache can be bypassed by rewriting a file while preserving mtime and size

Property Value
Severity Low
CWE CWE-697
Location src/security/skill-scanner.ts:78-96

Description

The skill-scanner uses an in-memory cache to avoid re-scanning files. Cache validity is based only on (mtimeMs, size, maxFileBytes).

This allows stale scan results to be reused if an attacker (or fast CI/filesystems with coarse timestamp granularity) rewrites a file such that:

  • The file content changes (e.g., introduces eval() / child_process.exec()), but
  • The file size stays identical, and
  • mtimeMs is unchanged (e.g., rewrite occurs within the same mtime granularity tick, or the attacker resets mtime using utimes).

In that case, scanFileWithCache() will return cached findings without reading the updated file, letting dangerous patterns evade detection in subsequent scans within the same long-running process.

Vulnerable code:

if (
  cached.size !== params.size ||
  cached.mtimeMs !== params.mtimeMs ||
  cached.maxFileBytes !== params.maxFileBytes
) {
  FILE_SCAN_CACHE.delete(params.filePath);
  return undefined;
}
return cached;

Recommendation

Strengthen cache invalidation so content changes cannot be hidden behind identical (mtime,size).

Recommended options (pick one):

  1. Include ctimeMs in the cache key (helps against mtime spoofing via utimes on many platforms):
// when stat-ing
const st = await fs.stat(filePath);
const cached = getCachedFileScanResult({
  filePath,
  size: st.size,
  mtimeMs: st.mtimeMs,
  ctimeMs: st.ctimeMs,
  maxFileBytes,
});
  1. Content hashing for high-assurance scans (most robust):
  • Store a fast hash (e.g., SHA-256/xxhash) of the last scanned content and compare it before reusing cached findings.
  1. Disable caching for security/audit runs, or provide an option like { disableCache: true } for callers that need correctness over performance.

Also update tests to cover the same-size rewrite case (i.e., content change with identical size) to prevent regressions.


Analyzed PR: #32191 at commit 3b7411c

Last updated on: 2026-03-02T23:13:24Z

dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants