Skip to content

fix(spend): refresh expired dashboard snapshots on return - #3107

Merged
steipete merged 2 commits into
steipete:mainfrom
Yuxin-Qiao:feat/spend-cache-ttl
Sep 8, 2026
Merged

fix(spend): refresh expired dashboard snapshots on return#3107
steipete merged 2 commits into
steipete:mainfrom
Yuxin-Qiao:feat/spend-cache-ttl

Conversation

@Yuxin-Qiao

@Yuxin-Qiao Yuxin-Qiao commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Returning to Usage & Spend on the same day could keep an old chart indefinitely because the date-window refresh did not load new inputs. This refreshes an expired dashboard snapshot on pane appearance or app activation, retaining the chart while the load runs.

The five-minute TTL starts when a load completes. Repeated activations do not interrupt an in-flight or manual refresh, and source failures or a bucket-day rollover bypass the TTL. The ordinary load preserves existing provider refresh policies: it rescans local Codex sources and captures independent-provider publications without forcing every provider's network refresh.

Maintainer repair of @Yuxin-Qiao's contribution, integrated with current main. Regression coverage includes the 299/300-second boundary, retained chart during loading, repeated activations, and reopening across midnight.

Validation on db18c43927d55349cda11edd1cb2058d701c2647:

  • make check passed; local and committed-branch autoreview are clean through P2.
  • Native live proof used a Developer ID-signed synthetic host linked to the freshly built production pane/controller objects, with dictionary-backed settings, isolated storage, a controlled clock, and a delayed fixture loader. Peekaboo hid/reopened the actual pane: at 299s generation stayed 1 and the total stayed $4; at 300s generation became 2 while $4 remained visible, then completed at $8.
  • Full make test passed: 1,033 selections across 87 groups; one existing OAuth-cache test recovered on the runner's single group retry, with no timeouts. Exact-head CI and all nine PR checks are green. Land-ready.

Native pane before expiry:

Synthetic dashboard before expiry

Expired snapshot retained during the new load:

Synthetic dashboard loading with retained data

Completed refresh:

Synthetic dashboard after refresh

Release-note context (collected in the batch's separate notes PR): Refresh stale Usage & Spend charts when returning to the pane, retain cached data while loading, and handle midnight rollover; thanks @Yuxin-Qiao.

@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

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

Copy link
Copy Markdown

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: 1f4bbb2cc7

ℹ️ 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".

Comment on lines +73 to +77
if !force,
let lastAt = self.lastSpendDashboardTokenFetchAt[provider.instanceID],
let lastScope = self.lastSpendDashboardTokenFetchScope[provider.instanceID],
lastScope == costScopeSignature,
self.spendDashboardTokenSnapshotPublicationForCurrentConfig(for: provider) != nil,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route a non-forced request through the TTL check

This TTL cannot fire through production code: the sole caller in SpendDashboardSource.makeRequest always passes force: true, while .refreshMissing invokes that caller only when no current publication exists—even though this condition requires one. Consequently, the new five-minute guard cannot suppress any dashboard token scan; the caller needs to preserve the build mode's forced/non-forced semantics or perform the TTL decision before the missing-publication predicate.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 20, 2026
@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed September 8, 2026, 12:07 AM ET / 04:07 UTC.

ClawSweeper review

What this changes

Refresh expired Usage & Spend snapshots when the pane reopens or the app becomes active, retaining the chart during loading and adding freshness-boundary tests.

Merge readiness

Ready for maintainer review

Keep open as a useful, merge-ready fix. Current main and v0.56.8 lack the expiry refresh path; no actionable introduced defects remain, and the maintainer’s head-specific native verification satisfies the proof requirement.

Priority: P2
Reviewed head: db18c43927d55349cda11edd1cb2058d701c2647

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) A focused repair with boundary regressions, production-pane runtime evidence, and completed head-specific validation.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The owner’s head-specific native report exercises the production pane/controller through hide-and-reopen interactions in an isolated signed host: 299 seconds skips reload, 300 seconds retains $4 during loading, then publishes $8. Screenshots could not be independently retrieved; the recorded runtime observations establish the changed behavior.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The owner’s head-specific native report exercises the production pane/controller through hide-and-reopen interactions in an isolated signed host: 299 seconds skips reload, 300 seconds retains $4 during loading, then publishes $8. Screenshots could not be independently retrieved; the recorded runtime observations establish the changed behavior.
Evidence reviewed 9 items Applicable repository policy: Read the complete root AGENTS.md. Applied its small-change, provider-isolation, focused-regression, and native-runtime guidance. No nested AGENTS.md or maintainer-note files were found under the relevant trees. Builds and tests were not executed during this read-only review.
Verified introduced scope: The merge-base-to-head delta contains two production files and two test files: production +23/-0 and tests +158/-6. Earlier parser, database migration, and retained OpenCodex changes are absent from this introduced patch. Gatekeeper changes only relocate six existing anchors.
Current-main necessity: Current main only updates configuration on appearance and refreshes the date window on activation. The controller skips unchanged configuration and successful same-day reloads. All four reviewed paths are unchanged between the pinned base and fetched main.
Findings None None.
Security None None.

How this fits together

CodexBar’s shared spend controller combines local usage scans and provider publications into a snapshot used by Usage & Spend and Overview. Pane lifecycle events now check snapshot age before requesting another ordinary load.

flowchart TD
  A[Pane opens or app activates] --> B[Check snapshot freshness]
  B -->|Fresh or loading| C[Keep current chart]
  B -->|Expired or failed| D[Ordinary spend load]
  E[Local scans and provider publications] --> D
  D --> F[Publish refreshed snapshot]
  F --> G[Usage and Spend and Overview]
Loading

Before merge

None.

Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test delta Production +23/-0; tests +158/-6 The small production addition reuses the existing loader; test growth covers expiry boundaries and retained presentation.

Technical review

Best possible solution:

Use the existing ordinary-load pipeline to refresh expired snapshots on return while preserving visible data, provider refresh policies, and manual-refresh semantics.

Do we have a high-confidence way to reproduce the issue?

Yes, from source: with unchanged configuration and no intervening publication, reopening or activating on the same day does not reload current-main inputs. This review did not execute a failing main build.

Is this the best way to solve the issue?

Yes. A completion-based freshness check at the two lifecycle entrypoints is a narrow repair that reuses the established load and publication machinery.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 2a71b479a5d4.

Labels

Label changes:

  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P2: Repairs stale spend-chart refresh behavior with limited scope and no evidence of an urgent runtime outage.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The owner’s head-specific native report exercises the production pane/controller through hide-and-reopen interactions in an isolated signed host: 299 seconds skips reload, 300 seconds retains $4 during loading, then publishes $8. Screenshots could not be independently retrieved; the recorded runtime observations establish the changed behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The owner’s head-specific native report exercises the production pane/controller through hide-and-reopen interactions in an isolated signed host: 299 seconds skips reload, 300 seconds retains $4 during loading, then publishes $8. Screenshots could not be independently retrieved; the recorded runtime observations establish the changed behavior.

Evidence

What I checked:

  • Applicable repository policy: Read the complete root AGENTS.md. Applied its small-change, provider-isolation, focused-regression, and native-runtime guidance. No nested AGENTS.md or maintainer-note files were found under the relevant trees. Builds and tests were not executed during this read-only review. (AGENTS.md:1, db18c43927d5)
  • Verified introduced scope: The merge-base-to-head delta contains two production files and two test files: production +23/-0 and tests +158/-6. Earlier parser, database migration, and retained OpenCodex changes are absent from this introduced patch. Gatekeeper changes only relocate six existing anchors. (Sources/CodexBar/SpendDashboardController.swift:1573, db18c43927d5)
  • Current-main necessity: Current main only updates configuration on appearance and refreshes the date window on activation. The controller skips unchanged configuration and successful same-day reloads. All four reviewed paths are unchanged between the pinned base and fetched main. (Sources/CodexBar/PreferencesSpendDashboardPane.swift:165, 2a71b479a5d4)
  • Latest release still lacks this wiring: The v0.56.8 pane likewise has no snapshot-expiry check on appearance or activation. The tag resolves to the supplied latest-release commit. (Sources/CodexBar/PreferencesSpendDashboardPane.swift:165, 6ef82690b4a7)
  • Freshness and existing load contract: The completion path stamps snapshot freshness; the new check bypasses the TTL for failures, clock reversal, and a different bucket day, while leaving in-flight and manual refreshes alone. It reuses ordinary loading, existing configuration validation, and retained-chart publication rather than adding a provider refresh policy or persistent cache format. (Sources/CodexBar/SpendDashboardController.swift:1573, db18c43927d5)
  • Focused regression coverage: Three added tests cover skipping at 299 seconds, loading at 300 seconds while retaining the prior chart, repeated freshness checks during loading, completion stamping, and reopening across midnight. Existing tests separately cover date-window rollover and failed-source retries. (Tests/CodexBarTests/SpendDashboardClockRolloverTests.swift:195, db18c43927d5)

Likely related people:

  • steipete: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • Yuxin-Qiao: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (48 earlier review cycles; latest 8 shown)
  • reviewed 2026-09-02T18:24:54.074Z sha 4640477 :: needs real behavior proof before merge. :: [P2] Refresh cache freshness when the dashboard becomes active | [P2] Capture native inputs before OpenCodex enrichment
  • reviewed 2026-09-02T18:53:42.453Z sha 02c8982 :: needs real behavior proof before merge. :: [P2] Schedule expiry for cached-prime results | [P2] Check dashboard staleness when the app becomes active | [P2] Capture the native input before OpenCodex enrichment
  • reviewed 2026-09-02T19:22:51.423Z sha 6dddd49 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-03T01:58:46.271Z sha 2c1362d :: needs real behavior proof before merge. :: [P2] Preserve backups for retained failed inputs
  • reviewed 2026-09-03T03:22:37.980Z sha 400c9c6 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-03T04:29:31.830Z sha 1baadbd :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-03T09:25:55.893Z sha 1baadbd :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-08T03:21:28.090Z sha db18c43 :: blocked before merge. :: none

@steipete

Copy link
Copy Markdown
Owner

CI failure is a real compile error:

Sources/CodexBar/SpendDashboardController.swift:1086:23: error: referencing operator function '==' on 'Equatable' requires that 'SpendDashboardController.LoadPhase' conform to 'Equatable'

LoadPhase needs Equatable (or match with a case/if case pattern instead of ==). Please fix and re-push.

@Yuxin-Qiao
Yuxin-Qiao force-pushed the feat/spend-cache-ttl branch from 4582171 to d3596a6 Compare August 21, 2026 09:57

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

Copy link
Copy Markdown

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: d3596a6a84

ℹ️ 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".

let shouldPrimeCachedCodex: Bool = self.cachedLoader != nil
&& !Set(Self.codexOwnershipByID(configuration.codexAccountIdentities).keys)
.isSubset(of: Set(self.loadedInputs.map(\.id)))
&& (phase == .ordinary || self.loadedInputs.isEmpty)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pattern-match the load phase instead of comparing it

LoadPhase does not conform to Equatable, and its reconciling(ForcedOutcome) payload prevents synthesized conformance, so phase == .ordinary produces a “binary operator '==' cannot be applied” compiler error and blocks every build. Preserve the prior if case .ordinary = phase pattern or introduce an explicit case-checking property; the repository-required test run would have caught this before handoff.

AGENTS.md reference: AGENTS.md:L23-L24

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

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: ac856d5c36

ℹ️ 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".

let identity = "\(logURL.path)|\(size)|\(mtime)"

if let cached = self.readCachedEntries(identity: identity), !cached.isEmpty {
if let cached = self.readCachedEntries(identity: identity, since: since) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply the report cutoff on cache misses

When usage.jsonl has changed since the previous read—as it normally does whenever a new request is appended—the identity check here misses and loadEntries reparses, deduplicates, sorts, and returns the entire lifetime log without applying since. Consequently, the new window only limits SQLite cache-hit reads, while active users continue hitting the unbounded latency and memory path this change is meant to avoid; apply the cutoff in the parser/miss path as well, while retaining the full cache if needed.

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

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: 73bc2a1a34

ℹ️ 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".

Comment on lines +246 to +248
for baseline in providerBaselines where mode.shouldRefresh(
hasPublication: baseline.publication != nil,
isDashboardTokenStale: store.spendDashboardTokenFetchIsStale(for: baseline.provider))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Trigger the TTL check when reopening the pane

When the pane is reopened after five minutes without a configuration change, SpendDashboardPane.onAppear calls update(configuration:), but the equality guard in SpendDashboardController.update returns before building a request, so the stale predicate added here is never evaluated. Fresh evidence in the current tree is that the patch now correctly passes the non-forced mode to the loader, while the unchanged controller guard still prevents that path from running on an ordinary reopen. The cached snapshot can therefore remain beyond the advertised TTL until a configuration/date change or manual refresh triggers another load.

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

let parsed = try OpenCodexUsageParser.parse(fileURL: logURL, fileManager: fileManager)
var unique: [String: OpenCodexUsageEntry] = [:]
for entry in parsed {
unique[entry.requestID] = entry

P2 Badge Apply the cutoff before parsing cache misses

When usage.jsonl changes, this cache-miss path still parses the entire lifetime file, materializes every entry in unique, sorts the full collection, and rewrites all entries before the since filter runs. Thus active logs remain subject to unbounded peak memory and refresh latency even though the returned array is windowed. Fresh evidence in the current tree is that the follow-up only filters deduped after these full-history operations; pass the cutoff into parsing or otherwise avoid materializing old entries on the report path.

ℹ️ 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".

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

Copy link
Copy Markdown

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: b577455ac9

ℹ️ 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".

Comment on lines +324 to +326
let rowsByPath = mode == .scanReady
? Dictionary(grouping: snapshot.usageRows, by: \.path)
: [:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve row metadata in aggregate report hydration

When cached Codex usage contains reasoning tokens or usage preceding a historical pricing cutoff, .aggregateReport discards the persisted rows and reconstructs them from day/model aggregates. Those synthesized rows omit both reasoning and timestampUnixMs, while buildCodexReportFromCache derives displayed reasoning totals and date-sensitive pricing from those fields. Consequently, the cache-first snapshot can report zero reasoning and price historical usage at current rates; retain the required metadata in the aggregate representation or decode the rows needed for an equivalent report.

Useful? React with 👍 / 👎.

Comment on lines +35 to +38
guard self.tokenSnapshotPublicationForCurrentProviderConfig(for: provider) != nil,
let legacyLast = self.lastTokenFetchAt[provider.instanceID]
else { return true }
return Date().timeIntervalSince(legacyLast) >= 5 * 60

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require full dashboard coverage before adopting legacy freshness

When the regular token pipeline has just published its default 30-day snapshot before the first dashboard open, this branch treats it as fresh solely from its timestamp even though the dashboard requests scanDays == 365. capturedTokenPublication then falls back to that legacy snapshot and skips the independent dashboard fetch, so the dashboard's 365-day/All view is populated with only 30 days of history until another refresh is triggered. Reuse legacy freshness only when its history coverage and scope satisfy the dashboard request.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Aug 21, 2026
@Yuxin-Qiao
Yuxin-Qiao force-pushed the feat/spend-cache-ttl branch from b577455 to d0c3479 Compare August 21, 2026 23:34

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

Copy link
Copy Markdown

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: d0c347973b

ℹ️ 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".

|| snapshot.metadata.timeZoneIdentifier == calendar.timeZone.identifier
else { return CostUsageCache() }
return Self.cache(from: snapshot)
return Self.cache(from: snapshot, mode: mode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip row tables in aggregate cache reads

When the Codex cache approaches its 25,000-row or 256 MB budget, .aggregateReport still calls readSnapshot(), whose implementation reads and copies every usage_rows.payload blob and every token snapshot before cache(from:mode:) discards those arrays. As a result, the new dashboard path avoids JSON decoding but still incurs full-cache SQLite I/O and memory allocation on first paint; use a mode-specific snapshot reader that does not query these tables.

Useful? React with 👍 / 👎.

@Yuxin-Qiao
Yuxin-Qiao force-pushed the feat/spend-cache-ttl branch from d0c3479 to d8c5ae9 Compare August 21, 2026 23:42

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

Copy link
Copy Markdown

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: d8c5ae9269

ℹ️ 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".

Comment on lines 50 to 51
if let cached = self.readCachedEntries(identity: identity, since: since) {
return cached

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back when the cache read fails

When the matching SQLite cache cannot be read completely—for example, sqlite3_step returns SQLITE_BUSY/SQLITE_ERROR, or every cached payload fails decoding—readCachedEntries currently returns an empty array, and this newly unconditional cache-hit branch accepts it instead of reparsing the valid JSONL source. The OpenCodex source is then published as confirmed empty and remains so until the log identity changes; distinguish a successfully completed zero-row query from read/decoding failures and return nil for the latter.

Useful? React with 👍 / 👎.

@Yuxin-Qiao
Yuxin-Qiao force-pushed the feat/spend-cache-ttl branch from 77b21f3 to 9cb04e2 Compare August 22, 2026 02:47

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

Copy link
Copy Markdown

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: 9cb04e241b

ℹ️ 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".

Comment on lines +82 to +83
WHERE timestamp >= ?
ORDER BY timestamp, request_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Index the timestamp used by windowed cache reads

When an existing OpenCodex cache contains a large lifetime history, this query still performs a full scan of entries and sorts the matches because the schema only indexes the request_id primary key, not timestamp. Thus every dashboard cache hit remains proportional to the entire cache rather than the 365-day window this change is intended to load; add a (timestamp, request_id) index and ensure existing schema-version-1 databases receive it.

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

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: 328a4360d6

ℹ️ 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".

Comment on lines +64 to +68
let snapshot = self.readSnapshot(skipRowTables: mode == .aggregateReport)
guard snapshot.metadata.timeZoneIdentifier == nil
|| snapshot.metadata.timeZoneIdentifier == calendar.timeZone.identifier
else { return CostUsageCache() }
return Self.cache(from: snapshot)
return Self.cache(from: snapshot, mode: mode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep aggregate-hydrated pricing rows trustworthy

When cache-first hydration must rebuild a report from aggregates rather than use an exact previous report, the synthesized token-bearing rows have no eventIndex, so codexRowCostBreakdown marks them unstable and rowCostIsTrusted is always false. Standard usage then falls back to aggregate pricing that excludes the newly separated reasoning tokens, while priority evidence disables that fallback entirely and produces a nil cost. Fresh evidence is that the current patch now synthesizes reasoning/timestamp metadata, but this aggregate-mode path still discards its row-level pricing, so cached spend remains lower or unavailable until the live scan finishes.

Useful? React with 👍 / 👎.

Comment on lines +173 to +174
Self.bindAggregateValues(aggregate, to: insert, startingAt: 4)
Self.bind(aggregate.earliestTimestampUnixMs, to: insert, at: 20)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind timestamps to the actual SQL placeholders

For every persisted file aggregate, this statement has only 18 parameters: the 14 values bound starting at index 4 end at 17, making earliest_timestamp_ms parameter 18 rather than 20. SQLite therefore returns SQLITE_RANGE for this ignored bind and inserts NULL for the timestamp; the global aggregate insert repeats the same off-by-two error at index 19 instead of 17. Fresh evidence is that the patch added timestamp persistence to preserve date-sensitive pricing, but cached historical rows still lose that metadata and resolve without their original pricing date.

Useful? React with 👍 / 👎.

@Yuxin-Qiao
Yuxin-Qiao force-pushed the feat/spend-cache-ttl branch from 328a436 to e4466e0 Compare August 22, 2026 09:08

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

Copy link
Copy Markdown

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: 63e8df340e

ℹ️ 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".

Comment on lines +79 to +80
$0 + ($1.inputTokens ?? 0) + ($1.cacheReadTokens ?? 0)
+ max(0, ($1.outputTokens ?? 0) - ($1.reasoningTokens ?? 0))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop subtracting reasoning from exclusive output

For this fixture, stored.data already exposes output exclusive of reasoning because the changed scanner stores deltaOutput - deltaReasoningTokens; expectedUnits was likewise updated to count exclusive output. Subtracting reasoningTokens here a second time therefore undercounts every reasoning-bearing entry and makes the final storedUnits == expectedUnits assertion fail. The repository-required full test run would expose this regression.

AGENTS.md reference: AGENTS.md:L23-L23

Useful? React with 👍 / 👎.

Comment on lines +35 to +36
if self.spendDashboardTokenSnapshotPublicationForCurrentConfig(for: provider) != nil {
return false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist the timestamp after a completed dashboard fetch

After the first successful or confirmed-empty dashboard scan, this branch treats the current publication as fresh whenever lastSpendDashboardTokenFetchAt is absent, but this change also removes the only production assignment to that dictionary and no success path replaces it. With an unchanged provider scope, the five-minute comparison is consequently never reached and ordinary pane reopens reuse the publication indefinitely; record the completion time when publishing a successful or empty result while leaving failures timestamp-free.

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

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: 4381ec51ae

ℹ️ 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".

Comment on lines +382 to +384
let rowsByPath = mode == .scanReady
? Dictionary(grouping: snapshot.usageRows, by: \.path)
: [:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve request boundaries in aggregate hydration

When a cached day/model contains multiple standard requests, this aggregate-only path discards their individual rows and later synthesizes one row containing the entire daily input. Long-context pricing is request-scoped, so two 150k-token requests below a 200k threshold become one 300k-token row; because that synthetic row has no eventIndex, makeCodexBilledDayEntry rejects its row cost and codexAggregateCostUSD returns nil once the aggregate exceeds the threshold. The cache-first dashboard therefore loses an otherwise computable cost until the live scan finishes; retain per-request threshold evidence or a trustworthy persisted computed cost.

Useful? React with 👍 / 👎.

@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 22, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

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

Copy link
Copy Markdown

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: 5fdf6d7d05

ℹ️ 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".

// back at the output rate (see `codexResolvedCostUSD`), keeping USD unchanged while
// making token buckets additive.
let deltaReasoningTokens = deltaReasoning ?? 0
let outputExcludingReasoning = max(0, deltaOutput - deltaReasoningTokens)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve reasoning after making stored output exclusive

When reasoning exceeds the non-reasoning output remainder, this representation change truncates reasoning in project/model analytics. For example, an event with 100 output and 60 reasoning now stores (output: 40, reasoning: 60); CodexLocalProjectUsageIndexer forwards those values to CodexModelsUsageFragment, whose initializer in CodexModelsAnalyticsModels.swift clamps reasoning to outputTokens, reducing it to 40. Update that downstream invariant for the new exclusive-output representation so analytics retain the actual reasoning count.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added the rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. label Aug 23, 2026

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

Copy link
Copy Markdown

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: 4640477b1e

ℹ️ 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".

Comment thread Sources/CodexBar/PreferencesSpendDashboardPane.swift
Comment on lines +1555 to +1557
Dictionary(uniqueKeysWithValues: request.capturedInputs
.filter { $0.sourceKind == .native }
.map { ($0.id, $0) })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve loaded Codex inputs before enrichment

When retained OpenCodex data is merged into exactly one native Codex account, that native input was created by codexSnapshotLoader and is not present in request.capturedInputs (Codex is represented there by codexRequests). Consequently this backup omits it, and clearExpiredOpenCodexSnapshot() cannot undo the enrichment at the missing-log deadline; if the subsequent Codex refresh is slow or fails and retains the previous input, expired OpenCodex spend remains embedded in the native row. Capture the complete pre-merge input set from the loader result rather than only the request's provider publications.

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown

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: 02c8982591

ℹ️ 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".

failedSourceIDs: request.unavailableSourceIDs,
openCodexObservation: openCodex.observation)
openCodexObservation: openCodex.observation,
freshUntil: openCodex.freshUntil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Schedule expiry for cached prime results

When an initial cache prime serves retained OpenCodex entries shortly before their missing-source deadline, this propagates freshUntil, but startLoad passes the result to applyCached, which ignores the deadline and schedules no expiry. The subsequent provider/Codex load can run well past that deadline, leaving expired OpenCodex spend visible until the full load completes; arm the expiry timer when applying the cached result as well.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 2, 2026
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Follow-up on the current head: refreshed the Spend dashboard gatekeeper anchors after subsequent upstream source shifts. The local ProviderArchitectureGatekeeperTests and full make check pass; CI is now queued for 2c1362d.

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

Copy link
Copy Markdown

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: 2c1362d485

ℹ️ 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".

Comment thread Sources/CodexBar/SpendDashboardController.swift Outdated

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

Copy link
Copy Markdown

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: 17bf479f7b

ℹ️ 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".

Comment thread Sources/CodexBar/SpendDashboardController.swift Outdated
Comment thread Sources/CodexBar/SpendDashboardSource+OpenCodex.swift Outdated
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Sep 3, 2026
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Sep 3, 2026
Integrate current main and retain the visible chart while ordinary loads
refresh an expired snapshot. Refresh across bucket-day rollover even
inside the TTL, and cover repeated activations during an in-flight load.

Co-authored-by: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com>
@steipete steipete changed the title feat(spend): cache-first and 5m TTL for dashboard fix(spend): refresh expired dashboard snapshots on return Sep 8, 2026
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 8, 2026
@steipete

steipete commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Maintainer verification complete on db18c43927d55349cda11edd1cb2058d701c2647.

  • make check passed; local and branch autoreview are clean through P2.
  • Full make test: 1,033 selections, 87 groups, no timeouts. One existing Claude OAuth cache-invalidation test recovered on the runner's single group retry.
  • Developer ID-signed native proof linked the freshly built production pane/controller into an isolated synthetic host. Peekaboo hid/reopened the actual pane at a controlled 299 seconds: generation 1 and $4 stayed unchanged. At 300 seconds, generation 2 retained $4 during the delayed load, then published $8. The app was terminated after proof. Unit regressions also cover midnight rollover and repeated activation during a load.
  • CI run 34182894825 succeeded on this exact head; all nine PR checks are green.

The inspected synthetic screenshots are in the PR body. Changelog entry is consolidated in #3491. Land-ready; no merge performed here. Thanks @Yuxin-Qiao.

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Sep 8, 2026
@steipete
steipete merged commit 80afaac into steipete:main Sep 8, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants