You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Usage & Spend dashboard took ~5 hours to publish Codex spend for one of my two Codex accounts because the lookback queue reseeded on every scan pass. Root cause: a stale scanSinceKey in the persisted lookback state triggered activeLookbackStateWasReset on every scan, which reseeded the entire queue (3,367 files) even though all files were already parsed and priced. The queue eventually drained fully between two scan boundaries and the dashboard published, but the recovery should take one scan pass, not five hours.
Bug Type
behavior bug
Release Blocker
No
Steps to Reproduce
Have a large Codex session corpus (3,879 files, ~3,500 in ambient ~/.codex/sessions, rest in CodexBar-managed homes). Two Codex accounts configured.
Let CodexBar build its cost cache. scan_metadata reaches completedFiles: 3879 / totalFiles: 3879.
Observe lookback_state.pendingFilePaths drain to zero during background passes (queue shrinks ~250-500 files per ~15 min pass).
Trigger a new scan (open Settings → Usage & Spend, or wait for the next background cycle). The scan re-enumerates and re-enqueues the entire lookback set. pendingFilePaths jumps back to ~3,367.
catchUpPending stays true for hours. The dashboard shows "Spend unavailable" for the affected account row until a drain pass happens to finish the whole queue between scan boundaries.
Expected Behavior
Once all files are complete and the lookback queue is empty, catchUpPending should flip to false and the dashboard should publish Codex spend on the next refresh. The queue should not re-enqueue files that were already processed and marked complete. A scan-window change should not reseed already-completed files.
Actual Behavior
The queue reseeded on every scan pass for ~5 hours before one drain pass completed between scan boundaries.
Timeline from a single session (all times UTC):
Time
pending
completed
trigger
04:42:48
0
3879/3879
background pass, queue drained
04:46:03
3,367
3878/3879
pane opened, scan re-enqueued entire lookback set
04:46:36
3,262
3878/3879
draining again
04:48:14
307
3878/3879
continued draining
Root cause: scanSinceKey mismatch triggers lookback state reset on every scan
CostUsageScanner.swift:5616 checks activeLookbackStateWasReset by comparing the cached lookback state's scanSinceKey and rootPaths against the freshly computed values:
When activeLookbackStateWasReset is true, shouldSeedBoundedQueue becomes true (line 5780), which calls seedOrExtendCodexActiveLookbackQueue and reseeds the entire pending queue from all known files.
The dashboard uses a 365-day scan window (SpendDashboardController.swift:168-170: activityDays = 365, scanDays = activityDays). So the fresh scanSinceKey is 2025-09-03 (365 days back from today). But the persisted lookback_state.scanSinceDay was stuck at 2026-06-05:
Every scan saw the scanSinceKey mismatch, activeLookbackStateWasReset = true, shouldSeedBoundedQueue = true, entire queue reseeded. The stale scanSinceDay couldn't update because the scanner only persists the new lookback state at the end of a completed scan, and the scan couldn't complete because the reseeded queue was too large to drain in one bounded pass.
The lookback_state.scanSinceDay of 2026-06-05 corresponds to a ~90-day window, likely the original scan window before the dashboard switched to 365 days. When the window widened, the cached lookback state's scanSinceKey became stale. The codexActiveLookbackState function (line 2775) returns the cached state when scanSinceKey matches, but when it doesn't match, it creates a fresh state with retainedPendingFilePaths (line 2787) and the new scanSinceKey. The fresh state is used in-memory but never persisted until a scan completes.
Resolution observed
After ~5 hours, one drain pass finished the entire queue before the next scan boundary. The scan completed, the fresh lookback state persisted (the lookback_state row is now gone entirely), and the publish gate opened:
The dashboard now renders the previously blocked account row. The bug is not a permanent deadlock on every corpus, but a race: the reseed loop continues until a drain pass happens to complete the full queue between scan boundaries. On a large corpus (3,879 files) with ~15-minute background cadence, that took roughly 5 hours.
Suggested fix direction
A scanSinceKey-only mismatch should not reseed already-completed files. When the window widens, the scanner should enqueue only files whose content days fall in the newly added range (between the old scanSinceKey and the new one), not re-enqueue all 3,879 files. Alternatively, the lookback state's scanSinceKey could be updated in-place when a window change is detected, without requiring a full scan completion. Either approach collapses the 5-hour recovery into a single scan pass.
Cost data was computed the whole time
The dashboard's JSON export (Usage & Spend > export) shows the affected account's data was fully computed while the UI showed "Spend unavailable":
The cost engine correctly attributed and priced all sessions. The "Spend unavailable" state was purely the publish gate. The model breakdown was also computed (gpt-5.6-sol, gpt-5.6-luna, gpt-5.6-terra, gpt-5.5, gpt-5.4).
Version
0.56.4 (build 135)
Environment
macOS 26.6.2 (25G83), Apple Silicon. Two Codex accounts (one team account managed home, one personal account managed home). 3,879 indexed session files.
Configuration Details
Two Codex accounts configured via CodexBar managed homes. Sessions routed through a wrapper that sets CODEX_HOME per account (not auth_profile). Ambient ~/.codex/sessions has ~3,500 files; managed homes have a small number of recently routed files. Both managed homes were re-authed with fresh codex login (previous tokens had expired).
[2026-09-03T22:18:38Z] [WARNING] Spend Dashboard Codex cost catch-up stopped because all pending account caches stalled
[2026-09-03T23:08:32Z] [WARNING] Spend Dashboard Codex cost catch-up stopped because all pending account caches stalled
[2026-09-04T02:45:50Z] [WARNING] Spend Dashboard Codex cost catch-up stopped because all pending account caches stalled
[2026-09-04T03:06:01Z] [WARNING] Spend Dashboard Codex cost catch-up stopped because all pending account caches stalled
[2026-09-04T04:42:57Z] [INFO] cost usage success provider=codex duration=8.79s today=$0.00 historyDays=90 windowCost=$10,615.66
Screenshots
Dashboard overview (30d) after the publish gate opened, showing Codex · #2 at $4,256.23 · 15B tokens in "By subscription" and the model breakdown:
Session breakdown, showing per-session rows for Codex · #2 all rendering with individual costs:
Note: the banner still reads "Local estimated history · Refreshing" with "stale data · Updated 14 minutes ago", 71 KB / 71 KB. This may clear on the next successful refresh; I'll report back if it persists.
Impact and Severity
Affected: users with large Codex session corpora and multiple accounts
Severity: blocks workflow. The Usage & Spend dashboard is the primary spend surface, and one account row shows "Spend unavailable" for hours
Frequency: every time the scan window changes on a large corpus (window widen, or possibly the daily rolling-window shift)
Consequence: cannot see per-account Codex spend in the dashboard for hours; must rely on the menu bar aggregate which doesn't break down by account
Corpus size: 3,879 files vs 537. The re-enumeration scales with corpus size, which may explain why the drain fix works on smaller corpora but stalls here.
What I tried that didn't help (before understanding the root cause):
Deleting the lookback_state row: triggered a full cold rebuild that re-enqueued the entire lookback set (3,367 files). Made it worse.
Waiting for the drain: the queue drains between scans but repopulates on the next pass.
TL;DR
The Usage & Spend dashboard took ~5 hours to publish Codex spend for one of my two Codex accounts because the lookback queue reseeded on every scan pass. Root cause: a stale
scanSinceKeyin the persisted lookback state triggeredactiveLookbackStateWasReseton every scan, which reseeded the entire queue (3,367 files) even though all files were already parsed and priced. The queue eventually drained fully between two scan boundaries and the dashboard published, but the recovery should take one scan pass, not five hours.Bug Type
behavior bug
Release Blocker
Steps to Reproduce
~/.codex/sessions, rest in CodexBar-managed homes). Two Codex accounts configured.scan_metadatareachescompletedFiles: 3879 / totalFiles: 3879.lookback_state.pendingFilePathsdrain to zero during background passes (queue shrinks ~250-500 files per ~15 min pass).pendingFilePathsjumps back to ~3,367.catchUpPendingstaystruefor hours. The dashboard shows "Spend unavailable" for the affected account row until a drain pass happens to finish the whole queue between scan boundaries.Expected Behavior
Once all files are complete and the lookback queue is empty,
catchUpPendingshould flip tofalseand the dashboard should publish Codex spend on the next refresh. The queue should not re-enqueue files that were already processed and marked complete. A scan-window change should not reseed already-completed files.Actual Behavior
The queue reseeded on every scan pass for ~5 hours before one drain pass completed between scan boundaries.
Timeline from a single session (all times UTC):
Root cause:
scanSinceKeymismatch triggers lookback state reset on every scanCostUsageScanner.swift:5616checksactiveLookbackStateWasResetby comparing the cached lookback state'sscanSinceKeyandrootPathsagainst the freshly computed values:When
activeLookbackStateWasResetis true,shouldSeedBoundedQueuebecomes true (line 5780), which callsseedOrExtendCodexActiveLookbackQueueand reseeds the entire pending queue from all known files.The dashboard uses a 365-day scan window (
SpendDashboardController.swift:168-170:activityDays = 365,scanDays = activityDays). So the freshscanSinceKeyis2025-09-03(365 days back from today). But the persistedlookback_state.scanSinceDaywas stuck at2026-06-05:Every scan saw the
scanSinceKeymismatch,activeLookbackStateWasReset = true,shouldSeedBoundedQueue = true, entire queue reseeded. The stalescanSinceDaycouldn't update because the scanner only persists the new lookback state at the end of a completed scan, and the scan couldn't complete because the reseeded queue was too large to drain in one bounded pass.The
lookback_state.scanSinceDayof2026-06-05corresponds to a ~90-day window, likely the original scan window before the dashboard switched to 365 days. When the window widened, the cached lookback state'sscanSinceKeybecame stale. ThecodexActiveLookbackStatefunction (line 2775) returns the cached state whenscanSinceKeymatches, but when it doesn't match, it creates a fresh state withretainedPendingFilePaths(line 2787) and the newscanSinceKey. The fresh state is used in-memory but never persisted until a scan completes.Resolution observed
After ~5 hours, one drain pass finished the entire queue before the next scan boundary. The scan completed, the fresh lookback state persisted (the
lookback_staterow is now gone entirely), and the publish gate opened:The dashboard now renders the previously blocked account row. The bug is not a permanent deadlock on every corpus, but a race: the reseed loop continues until a drain pass happens to complete the full queue between scan boundaries. On a large corpus (3,879 files) with ~15-minute background cadence, that took roughly 5 hours.
Suggested fix direction
A
scanSinceKey-only mismatch should not reseed already-completed files. When the window widens, the scanner should enqueue only files whose content days fall in the newly added range (between the oldscanSinceKeyand the new one), not re-enqueue all 3,879 files. Alternatively, the lookback state'sscanSinceKeycould be updated in-place when a window change is detected, without requiring a full scan completion. Either approach collapses the 5-hour recovery into a single scan pass.Cost data was computed the whole time
The dashboard's JSON export (Usage & Spend > export) shows the affected account's data was fully computed while the UI showed "Spend unavailable":
{ "displayName": "Codex · #2", "id": "codex:subs@kessler.io", "sourceKind": "native", "totalCost": 4256.232196680002, "totalTokens": 14660778502 }The cost engine correctly attributed and priced all sessions. The "Spend unavailable" state was purely the publish gate. The model breakdown was also computed (gpt-5.6-sol, gpt-5.6-luna, gpt-5.6-terra, gpt-5.5, gpt-5.4).
Version
0.56.4 (build 135)
Environment
macOS 26.6.2 (25G83), Apple Silicon. Two Codex accounts (one team account managed home, one personal account managed home). 3,879 indexed session files.
Configuration Details
Two Codex accounts configured via CodexBar managed homes. Sessions routed through a wrapper that sets
CODEX_HOMEper account (notauth_profile). Ambient~/.codex/sessionshas ~3,500 files; managed homes have a small number of recently routed files. Both managed homes were re-authed with freshcodex login(previous tokens had expired).Logs
Redacted log excerpts (current rotation,
~/Library/Logs/CodexBar/CodexBar.log):Screenshots
Dashboard overview (30d) after the publish gate opened, showing Codex · #2 at $4,256.23 · 15B tokens in "By subscription" and the model breakdown:
Session breakdown, showing per-session rows for Codex · #2 all rendering with individual costs:
Note: the banner still reads "Local estimated history · Refreshing" with "stale data · Updated 14 minutes ago", 71 KB / 71 KB. This may clear on the next successful refresh; I'll report back if it persists.
Impact and Severity
Additional Information
Related: #3071 (closed, fix in #3075), #3316 (open, restart loop variant).
This is distinct from #3316 in two ways:
What I tried that didn't help (before understanding the root cause):
lookback_staterow: triggered a full cold rebuild that re-enqueued the entire lookback set (3,367 files). Made it worse.