fix(codex): re-scan sessions resumed in older date partitions on a warm cache - #2559
Conversation
|
Codex review: needs maintainer review before merge. Reviewed August 2, 2026, 8:59 PM ET / August 3, 2026, 00:59 UTC. ClawSweeper reviewWhat this changesThe PR makes Codex warm-cache refreshes progressively discover and rescan recently modified rollout files in older date partitions without enabling corpus-wide recursive discovery on every refresh. Merge readinessThis PR remains necessary: current Priority: P2 Review scores
Verification
How this fits togetherCodexBar’s Codex cost scanner enumerates local rollout files, parses their token usage, and persists results in a cache used by the CLI and Usage & Spend UI. This change affects the bounded discovery stage that feeds files into the existing parser and catch-up flow. flowchart LR
A[Codex rollout folders] --> B[Session discovery]
B --> C[Older-partition lookback]
C --> D[Shared scan budget]
D --> E[Persistent cache state]
E --> F[Usage parser]
F --> G[CLI and Spend Dashboard totals]
Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Land the bounded, resumable older-partition discovery path after the exact-head platform checks pass, keeping whole-corpus recursive discovery restricted to cold-start cycles. Do we have a high-confidence way to reproduce the issue? Yes. The added filesystem-only regression creates a warm cache, resumes a rollout in an older partition, and compares bounded warm catch-up with a forced rescan; current main’s cold-only call site explains why the path fails there. Is this the best way to solve the issue? Yes. Persisting bounded partition progress and discovered paths is the narrow maintainable repair: it restores discovery without making every warm refresh perform an unbounded recursive corpus walk. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 4f8e3049a86c. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (8 earlier review cycles)
|
…rm cache A Codex session whose rollout lives outside the scan window was unreachable by every candidate source once the cache was warm: the partition listing only walks day directories inside the window, and `cachedCodexSessionFiles` requires the path to already be in `cache.files`. The mtime lookback that would catch it was gated behind `cache.files.isEmpty || plan.rootsChanged`, so resuming such a session appended usage that was never scanned again until a forced rescan. Run the lookback on every refresh instead of only cold ones. The unbounded recursive walk of the sessions root stays cold-cache only, so warm refreshes pay just the bounded partition lookback (`codexActiveSessionLookbackDays`) rather than a cost that scales with the whole corpus. Verified red→green on Linux: before, a warm refresh reported 110 tokens where a forced rescan reported 4455; after, both agree.
31cf29b to
d35be70
Compare
|
Rebased onto Reconciliation with #2525:
The earlier CI failure was not a product defect or the #2573 spend-dashboard clock fixture. The substantive failure was Proof on the rebased head:
Commits:
CI/review follow-up: the first rebased run exposed one Linux-only assertion in the new budget test. A later ClawSweeper review correctly found that a budget smaller than the partition offset would restart at the oldest day and could starve later partitions. The final implementation persists per-root day cursors and discovered candidate paths, keeps cold recursive discovery confined to its original cycle, and reconciles |
|
Exact-head follow-up for
|
Closes #2558.
Problem
A Codex session whose rollout file lives in a date partition outside the scan window is unreachable by every candidate source once the cache is warm:
listCodexSessionFilesByDatePartitionwalks onlyYYYY/MM/DDdirectories inside the window (CostUsageScanner.swift:1462).cachedCodexSessionFilesrequires the path to already be incache.files— and it never got there.The mtime lookback that exists precisely to catch active sessions was gated behind:
i.e. cold cache only. So resuming such a session appends usage that is never scanned again. Recovery needs
forceRescan, a roots change, or deleting the cache —pricingChanged/needsProjectMetadataMigrationsetforceFullScanbut do not add the file to the candidate list, so they do not recover it either.Silent: no error, no diagnostic, just Codex cost and token totals that read low across
codexbar cost,serve /cost,/dashboard/v1/snapshot, Usage & Spend, and model analytics.Fix
Run the lookback on every refresh rather than only cold ones.
The important part is the cost.
listCodexRecentlyModifiedFilesdoes two things: a bounded partition lookback (codexActiveSessionLookbackDaysextra day directories) and an unbounded recursive walk of the whole sessions root. Only the bounded half now runs when the cache is warm — the recursive walk stays cold-cache only, so a warm refresh does not gain a cost that scales with corpus size. That matters here given #2538.17 lines in one function plus the call site.
Proof (Linux,
swift:6.3.3)Pure filesystem — no network, no credentials. Write an in-window session so the cache is warm, write a second rollout into an older partition, scan, append a turn to the old rollout as resuming does, scan again, compare against
forceRescan.Before (stock
78523f4a):After:
I reverted the fix and re-ran to confirm the test genuinely fails without it, rather than trusting a green run.
Full Linux suite 318/318,
swiftformat --lintclean,swiftlint --strict0 violations / 1668 files.Scope note
The fix covers resumes within
codexActiveSessionLookbackDaysof the window start, which is what the bounded lookback is designed for and matches the test. A session resumed after a gap longer than that still needs the recursive walk, and I deliberately did not enable that on warm refreshes because of its cost profile. Happy to extend if you'd prefer broader coverage over the cheaper refresh.This file is currently busy (#2520, #2525, #2538) — I kept the change as small as I could to minimise conflicts, and I'm happy to rebase whenever.