Skip to content

feat(spend): cache-first and 5m TTL for dashboard - #3107

Open
Yuxin-Qiao wants to merge 30 commits into
steipete:mainfrom
Yuxin-Qiao:feat/spend-cache-ttl
Open

feat(spend): cache-first and 5m TTL for dashboard#3107
Yuxin-Qiao wants to merge 30 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

Summary

Rebased onto current main (2db6a57, includes #3120 parity scanner) and reconciled the cache-first spend dashboard + 5m TTL feature on top of it.

Reconciliation with landed #3120 semantics

  • Scanner: main's tokscale-parity scanner is kept wholesale (inclusive output, multi-field stale detection, bare usage exec aliases). The branch no longer carries a divergent snapshot.
  • Cache-first hydration: retained from this PR; aggregate hydration distributes reasoning across standard and priority output capacity, preserving the aggregate reasoning total instead of clamping it onto one row.
  • Gatekeeper: line-number anchors realigned for the shifted SpendDashboardController/Pane files (anchors/fingerprints unchanged).
  • Removed obsolete branch-only tokscale parity test superseded by fix(codex): tokscale parity for token counts - max cached, clamped, reasoning split, stale #3120 coverage.

Correctness repairs from review

  • TTL expiry: a successful or confirmed-empty dashboard fetch stamps freshness after the awaited token fetch completes, so a slow request does not consume its own 5-minute TTL.
  • Fresh pane reopen: a successful dashboard snapshot now stays cache-only for five minutes; repeated opens do not start SpendDashboardSource.load or rescan Codex accounts. Snapshot freshness has its own completion clock, separate from the reporting-window loadedAt clock, so same-day activations cannot postpone expiry.
  • Aggregate SQL timestamps: file and global aggregate inserts bind earliest_timestamp_ms at the actual placeholder indices, preserving pricing dates.
  • Exact cutover parity: restored the strict assertion and fixed the fixture oracle mismatch by restoring main's scannerUnits definition.
  • Mixed pricing-mode reasoning: aggregate hydration distributes reasoning by each mode's output capacity, with regression coverage for 10 standard output + 100 priority output + 80 reasoning.
  • Bounded OpenCodex warm-cache reads: the requested report cutoff is bound in the SQLite entries query (timestamp >= ?) so long-lived caches do not decode lifetime history before filtering.
  • Incremental OpenCodex race fallbacks: both full-reload fallbacks retain the requested report cutoff.
  • v1 cache migration: a compatible v1 cache is migrated through the writable schema path before the read-only cache hit is attempted.
  • Pricing-era aggregate hydration: aggregate-only snapshots persist compact per-era/per-mode segments, so rows on opposite sides of the GPT-5.6 pricing cutoff keep their own read-time pricing date.
  • Resolved pricing aliases: aggregate-only segments retain the resolved billable model (for example, codex-auto-review -> gpt-5.4) in both persistence and grouping keys.

Current-head proof

Current pushed head: e468f364cdc8962cd4e74f385fcac41bc6c699a9

The deterministic proof uses isolated temporary stores and no provider credentials:

$ swift test --filter 'OpenCodexUsageStoreWindowTests|OpenCodexUsageStoreIncrementalTests|CostUsageStoreAggregateModeTests|SpendDashboardForceStateMachineTests|SpendDashboardTokenActivityIntegrationTests|SpendDashboardSourceConcurrencyTests|SpendDashboardClockRolloverTests'
# 65 tests passed across 7 suites, including v1 migration, warm-cache window binding,
# incremental race fallbacks, pricing-cutoff and resolved-alias parity, cache/TTL,
# and dashboard publication paths.

$ make check
# SwiftFormat: 0 files require formatting; SwiftLint: 0 violations

Current-head CI: https://github.com/steipete/CodexBar/actions/runs/32889384161

The current-head exact review has been requested; no live provider credentials are used.

@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.

@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 real behavior proof before merge. Reviewed August 25, 2026, 2:08 PM ET / 18:08 UTC.

ClawSweeper review

What this changes

This PR adds cache-first Spend dashboard hydration, a five-minute refresh TTL, aggregate SQLite reads for Codex usage, and windowed OpenCodex cache loading.

Merge readiness

Blocked until real behavior proof from a real setup is added - 9 items remain

Keep open: the cache-first path still has four concrete P2 correctness defects, and the supplied evidence is test-only rather than an after-fix dashboard run.

Priority: P2
Reviewed head: 49147463fdf0f6a24ac029255bf27762e05da89a

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) The patch has meaningful focused coverage, but four correctness blockers and missing real dashboard proof keep it below merge-ready quality.
Proof confidence 🦪 silver shellfish (2/6) Needs real behavior proof before merge: Focused tests, lint, and CI are useful supplemental evidence but not after-fix real dashboard proof; add a redacted fresh-bundle run showing cached first load, reuse before five minutes, and refresh after expiry. After updating the PR body, ClawSweeper should re-review automatically; otherwise ask a repository writer to comment @clawsweeper re-review.
Patch quality 🦐 gold shrimp (3/6) 4 actionable review findings remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: Focused tests, lint, and CI are useful supplemental evidence but not after-fix real dashboard proof; add a redacted fresh-bundle run showing cached first load, reuse before five minutes, and refresh after expiry. After updating the PR body, ClawSweeper should re-review automatically; otherwise ask a repository writer to comment @clawsweeper re-review.
Evidence reviewed 6 items TTL starts before the awaited fetch: The code captures now before loadTokenUsageSnapshot awaits, then records that old value as the successful-fetch timestamp; a slow fetch therefore consumes part or all of the advertised five-minute lifetime before publication.
Reload-race paths lose the window cutoff: The normal load path supplies since, but both incremental race fallbacks invoke fullReload without it, using that method's nil default and returning lifetime entries.
Read-only v1 cache fallback cannot migrate: A copied v1 cache is rejected by the read-only version guard before the writable schema migration runs; if the JSONL source is temporarily unreadable, its otherwise valid cached entries cannot be used.
Findings 4 actionable findings [P2] Stamp the TTL after the token fetch completes
[P2] Thread the report cutoff through reload races
[P2] Migrate v1 caches before using read-only fallback
Security None None.

Live Verification

Command: swift test --filter OpenCodexUsageStoreWindowTests

Result: FAIL (failed) — execution before step 1 run: sh -lc pnpm install --ignore-scripts --frozen-lockfile failed: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-11.24.0.tgz

sh -lc pnpm install --ignore-scripts --frozen-lockfile failed: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-11.24.0.tgz

Assertions:

  • FAIL expect_output: Test run with

How this fits together

The Spend dashboard combines provider snapshots and local Codex/OpenCodex usage into a published cost view. This change sits between persisted usage caches and dashboard publication, affecting refresh frequency, historical-window loading, and displayed cost accuracy.

flowchart LR
    A[Dashboard pane opens] --> B[Request builder]
    B --> C[Five-minute freshness check]
    C --> D[Provider snapshots]
    C --> E[Local usage caches]
    D --> F[Cost reconciliation]
    E --> F
    F --> G[Published Spend dashboard]
Loading

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: Focused tests, lint, and CI are useful supplemental evidence but not after-fix real dashboard proof; add a redacted fresh-bundle run showing cached first load, reuse before five minutes, and refresh after expiry. After updating the PR body, ClawSweeper should re-review automatically; otherwise ask a repository writer to comment @clawsweeper re-review.
  • Stamp the TTL after the token fetch completes (P2) - now is captured before the awaited token fetch, then stored here as the successful fetch time. A slow request can make a newly published snapshot immediately stale; capture a completion timestamp after loadTokenUsageSnapshot returns.
  • Thread the report cutoff through reload races (P2) - Both race fallbacks at lines 258 and 274 call fullReload without since, so an append/rotation race returns the entire lifetime log through the new cache-first dashboard path. Forward the requested cutoff on every fallback.
  • Migrate v1 caches before using read-only fallback (P2) - This version check rejects a compatible copied v1 cache before ensureSchema can add the index and set version 2. If the JSONL log is unreadable, the valid cache cannot be used; migrate before the read-only fallback or explicitly support the v1 read.
  • Retain pricing-era granularity in aggregate rows (P2) - Every reconstructed row receives the aggregate's earliest timestamp. A single dashboard day/model can span a historical pricing cutoff, so this prices all of its tokens at the older rate instead of matching the scan-ready rows; persist per-era aggregates or an authoritative computed cost.
  • Resolve merge risk (P1) - Merging can display inaccurate historical Codex spend when a dashboard day spans a pricing-rate cutoff.
  • Resolve merge risk (P2) - A copied v1 OpenCodex cache cannot provide the advertised offline fallback while the source log is unreadable.
  • Resolve merge risk (P1) - The local checkout could not complete a fresh current-main comparison because required promisor objects could not be fetched.
  • Complete next step (P2) - The contributor must resolve the concrete patch findings and provide real behavior proof; the external-contributor proof gate is not something an automated repair lane can establish.

Findings

  • [P2] Stamp the TTL after the token fetch completes — Sources/CodexBar/UsageStore+SpendDashboardTokenCost.swift:150
  • [P2] Thread the report cutoff through reload races — Sources/CodexBarCore/Vendored/OpenCodexUsage/OpenCodexUsageStore.swift:258-274
  • [P2] Migrate v1 caches before using read-only fallback — Sources/CodexBarCore/Vendored/OpenCodexUsage/OpenCodexUsageStore.swift:300
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch scope 26 files affected; +968, -144 lines The change crosses dashboard refresh, two persistent-cache formats, and scanner reconciliation, so upgrade and cache-equivalence behavior need review beyond focused tests.

Merge-risk options

Maintainer options:

  1. Repair cache semantics before merge (recommended)
    Carry the cutoff through all reload fallbacks, migrate compatible copied caches before fallback reads, timestamp completion after fetch, and preserve pricing-era fidelity with focused regressions.
  2. Pause aggregate hydration
    If pricing-era persistence is not ready for this PR, keep the existing scan-ready cache path rather than landing a dashboard that can misprice historical usage.

Technical review

Best possible solution:

Preserve exact pricing-era information in aggregate storage, carry the requested report cutoff through every reload fallback, migrate compatible v1 cache data before read-only fallback, and start freshness at successful fetch completion.

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

Yes—source-reproducible. The four failures follow deterministically from the TTL timestamp, reload fallback calls, read-only schema guard, and single-timestamp aggregate reconstruction paths.

Is this the best way to solve the issue?

No. The cache-first implementation needs to preserve its window, offline fallback, TTL, and historical-pricing invariants before it is a safe dashboard optimization.

Full review comments:

  • [P2] Stamp the TTL after the token fetch completes — Sources/CodexBar/UsageStore+SpendDashboardTokenCost.swift:150
    now is captured before the awaited token fetch, then stored here as the successful fetch time. A slow request can make a newly published snapshot immediately stale; capture a completion timestamp after loadTokenUsageSnapshot returns.
    Confidence: 0.99
  • [P2] Thread the report cutoff through reload races — Sources/CodexBarCore/Vendored/OpenCodexUsage/OpenCodexUsageStore.swift:258-274
    Both race fallbacks at lines 258 and 274 call fullReload without since, so an append/rotation race returns the entire lifetime log through the new cache-first dashboard path. Forward the requested cutoff on every fallback.
    Confidence: 0.99
  • [P2] Migrate v1 caches before using read-only fallback — Sources/CodexBarCore/Vendored/OpenCodexUsage/OpenCodexUsageStore.swift:300
    This version check rejects a compatible copied v1 cache before ensureSchema can add the index and set version 2. If the JSONL log is unreadable, the valid cache cannot be used; migrate before the read-only fallback or explicitly support the v1 read.
    Confidence: 0.98
  • [P2] Retain pricing-era granularity in aggregate rows — Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+CodexCache.swift:1103
    Every reconstructed row receives the aggregate's earliest timestamp. A single dashboard day/model can span a historical pricing cutoff, so this prices all of its tokens at the older rate instead of matching the scan-ready rows; persist per-era aggregates or an authoritative computed cost.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 0a1aa53598c9.

Labels

Label changes:

  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🦪 silver shellfish, so this older rating label is no longer current.

Label justifications:

  • P2: The PR can publish incorrect spend or bypass expected cache behavior, but the affected paths are bounded to dashboard/cache flows.
  • merge-risk: 🚨 compatibility: The PR changes persisted SQLite schemas and cache fallback semantics for existing local usage data.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: Focused tests, lint, and CI are useful supplemental evidence but not after-fix real dashboard proof; add a redacted fresh-bundle run showing cached first load, reuse before five minutes, and refresh after expiry. After updating the PR body, ClawSweeper should re-review automatically; otherwise ask a repository writer to comment @clawsweeper re-review.

Evidence

What I checked:

Likely related people:

  • Yuxin-Qiao: Authored the current cache/TTL implementation and earlier merged related Spend dashboard work in the supplied history. (role: recent Spend-dashboard contributor; confidence: high; commits: 49147463fdf0, 6538ac34597e; files: Sources/CodexBar/UsageStore+SpendDashboardTokenCost.swift, Sources/CodexBarCore/Vendored/OpenCodexUsage/OpenCodexUsageStore.swift, Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+CodexCache.swift)
  • steipete: The repository owner explicitly directed reconciliation of this cache-first work with the landed scanner semantics and reviewed the affected CostUsage cluster. (role: owner reviewer and adjacent scanner owner; confidence: medium; commits: d92783689570, 1cf98b330a79; files: Sources/CodexBar/SpendDashboardController.swift, Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Fix the four cache/TTL correctness findings with focused regressions.
  • Add redacted fresh-bundle dashboard evidence for cache-first loading, pre-expiry reuse, and post-expiry refresh.

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 (24 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-23T13:14:47.453Z sha 5fdf6d7 :: needs real behavior proof before merge. :: [P1] Restore exact cutover parity assertion | [P2] Record successful fetch completion time | [P2] Preserve reasoning in project analytics
  • reviewed 2026-08-23T15:42:20.614Z sha 5fdf6d7 :: needs real behavior proof before merge. :: [P1] Record the completed dashboard fetch time | [P1] Preserve additive reasoning in project analytics | [P2] Store earliest timestamps during aggregate merges | [P1] Restore the exact cutover parity assertion
  • reviewed 2026-08-23T22:07:10.038Z sha cc4195d :: needs real behavior proof before merge. :: [P2] Record the completed dashboard fetch time | [P1] Preserve additive reasoning in project analytics | [P1] Restore exact cutover parity coverage
  • reviewed 2026-08-23T22:53:26.054Z sha a84aa6b :: needs real behavior proof before merge. :: [P1] Record completed dashboard fetch time | [P1] Preserve additive reasoning in project analytics | [P1] Restore exact cutover parity coverage
  • reviewed 2026-08-23T23:31:02.873Z sha feaee6d :: needs real behavior proof before merge. :: [P1] Record successful dashboard fetch completion time | [P1] Preserve additive reasoning in project analytics | [P2] Bind aggregate timestamps to valid SQL placeholders | [P1] Restore exact cutover parity coverage
  • reviewed 2026-08-25T10:32:32.671Z sha c3927f7 :: needs real behavior proof before merge. :: [P1] Price inclusive output without re-adding reasoning
  • reviewed 2026-08-25T11:13:59.146Z sha 4e8391e :: needs real behavior proof before merge. :: [P2] Filter full-reload results by the report window | [P2] Create the timestamp index required by the v2 cache | [P2] Retain request boundaries for aggregate cached pricing
  • reviewed 2026-08-25T12:31:08.683Z sha 0f545a6 :: needs real behavior proof before merge. :: [P2] Push the history window into the SQLite cache query

@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: ff7fc29012

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

if let cached = self.readCachedState(), Self.canReuseCursor(cached.parseCursor, identity: identity) {
if identity.size == cached.parseCursor.parsedOffset {
return cached.entries
return Self.filteredEntries(cached.entries, 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 window before hydrating cached entries

When the cache contains a long OpenCodex history, readCachedState() has already queried and decoded every row before this in-memory since filter runs; readEntries still uses an unbounded SELECT ... FROM entries ORDER BY timestamp, request_id. Consequently, cache-hit I/O and memory remain proportional to the lifetime log rather than the requested 365-day window. Fresh evidence in the current tree is that the timestamp index was added, but the read query never binds a timestamp predicate, so pass since into the cache reader and filter in SQL.

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: 0f545a6070

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

cached: aggregate.priorityCachedTokens,
output: aggregate.priorityOutputTokens,
mode: "priority",
reasoning: hasStandardTokens ? 0 : syntheticReasoning)

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 Distribute reasoning across both pricing modes

When a day/model aggregate contains both standard and priority usage and its reasoning total exceeds the standard output, this assigns zero reasoning to the priority row while append clamps the entire total to the standard output. For example, 10 standard output tokens plus 100 priority output tokens and 80 reasoning tokens hydrate as only 10 reasoning tokens, so the cache-first dashboard underreports reasoning until the live scan finishes; allocate the remainder to the priority row while preserving the aggregate total.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Final-head proof for PR #3107:

  • Head: 0f545a60702eb0249e37a9936860bb0cd87124cd
  • CI run: https://github.com/steipete/CodexBar/actions/runs/32847797257 — completed successfully; all 9 checks pass, including both macOS shards and lint-build-test
  • Local focused proof: ProviderArchitectureGatekeeperTests passed (38 tests); SwiftFormat lint and strict SwiftLint passed on the touched test file
  • The latest commit only updates the provider-gatekeeper anchors to match the current source locations; no production behavior changed

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 25, 2026

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

Copy link
Copy Markdown
Contributor Author

Fix for the current-head P2 review finding:

  • New head: 3399e04e1b2bbf8cec0a40e706c8fb29434fca3b
  • Mixed standard/priority aggregate hydration now distributes reasoning by each mode's output capacity, preserving the aggregate reasoning total instead of clamping it all to the standard row
  • Added regression coverage for 10 standard output + 100 priority output + 80 reasoning; aggregate and cutover focused suites pass locally (6 tests)
  • make check passes locally; SwiftFormat and strict SwiftLint report zero violations
  • CI for this new head will rerun after the ref update

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 25, 2026

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@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: 3399e04e1b

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

standardTokens: 0,
priorityTokens: 0)
priorityTokens: 0,
earliestTimestampUnixMs: rows.compactMap(\.timestampUnixMs).min())

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 pricing-era boundaries during aggregate hydration

When one file/day/model contains usage on both sides of a date-sensitive pricing cutoff—for example, a non-UTC dashboard day that straddles the UTC cutoff—persisting only the earliest timestamp causes every synthetic aggregate row to be priced using the earlier rate. The scan-ready report prices each original row with its own timestamp, so cache-first spend diverges until the live scan finishes; preserve separate aggregates per pricing era or a trustworthy computed cost instead of one minimum timestamp.

Useful? React with 👍 / 👎.

Comment on lines +440 to +443
if version == 1, Self.databaseHasEntriesTable(db) {
if self.ensureTimestampIndex(db) {
Self.setUserVersion(db, Self.schemaVersion)
return

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 Migrate copied v1 caches before the read-only guard

When a v1 payload has been copied into opencodex-usage-v2.sqlite and usage.jsonl is temporarily unreadable, this migration never runs: readCachedState() opens read-only and rejects any version other than 2, while ensureSchema() is invoked only after a writable open reached through the full-reload path. The valid cached entries therefore cannot provide the promised non-destructive fallback; perform the v1 migration before rejecting the cached read.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Current-head repair and proof for PR #3107:

  • Head: 49147463fdf0f6a24ac029255bf27762e05da89a
  • Fixed the remaining P2: OpenCodex warm-cache reads now bind since in SQLite (timestamp >= ?) before hydrating rows; the timestamp index is used by the bounded query.
  • Added a cache-hit regression that seeds old/new entries, makes the log unreadable, and records that only new is decoded.
  • Local proof: OpenCodex window + incremental suites passed (27 tests); Spend dashboard cache/TTL/publication suites passed (28 tests); make check passed with SwiftFormat 0 files and SwiftLint 0 violations.
  • New-head CI is running: https://github.com/steipete/CodexBar/actions/runs/32881252928

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 25, 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: 49147463fd

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

// A current successful fetch owns freshness from here, including the
// confirmed-empty path below; without this stamp the published snapshot would
// bypass the five-minute TTL forever.
self.lastSpendDashboardTokenFetchAt[provider.instanceID] = now

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 Start the TTL after the fetch completes

When a dashboard token fetch takes a substantial portion of the five-minute interval, this stores now, which was captured before loadTokenUsageSnapshot was awaited, so a successful result can already be stale—or immediately expire—when it is published and reopening the pane triggers another expensive fetch. Fresh evidence beyond the earlier missing-timestamp finding is that the timestamp is now assigned, but it still records the attempt start rather than the successful completion time.

Useful? React with 👍 / 👎.

logURL: URL,
identity: LogIdentity,
fileManager: FileManager,
since: Date? = 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 Preserve the report cutoff across reload race fallbacks

When an incremental read races an append and parseLog throws ChangedUnderReadError, incrementalReload invokes this newly parameterized fullReload without forwarding since; a second file-identity race also returns parsed.entries directly. Those paths therefore return the entire lifetime log to the dashboard instead of the requested window, restoring the unbounded sorting and aggregation cost under active writes. Fresh evidence beyond the earlier cache-miss cutoff finding is that ordinary reload exits now filter correctly, while these race exits still take the new default nil cutoff.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Current-head repair and proof for PR #3107:

  • Head: 1559fb8cf195c6c66cb9757b6c662386273033fb
  • Fixed all four findings from the prior exact review:
    • freshness is stamped after token fetch completion;
    • OpenCodex incremental race fallbacks retain since;
    • compatible v1 caches migrate before the read-only fallback;
    • aggregate-only Codex hydration persists compact pricing-era/mode segments across the GPT-5.6 cutoff.
  • Local proof: 59 focused tests passed across 6 suites; make check reports 0 SwiftFormat files and 0 SwiftLint violations.
  • Current CI run: https://github.com/steipete/CodexBar/actions/runs/32884912889

@clawsweeper please re-review this exact head.

@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: 1559fb8cf1

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

!self.isRefreshing,
!self.phase.manualRefreshOutstanding
else { return }
self.startLoad(configuration: configuration, phase: .ordinary)

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 Avoid full reloads while the TTL is fresh

On every pane reopen after the initial load, this unconditionally starts an ordinary load even when all dashboard data is still inside the five-minute TTL. The request builder suppresses only the independent provider refreshes; it still includes every Codex request, and handleBuiltRequest still invokes SpendDashboardSource.load, which scans each Codex account and reloads OpenCodex. Consequently, repeatedly closing and reopening the pane within five minutes still shows a refresh and performs the expensive work the TTL is intended to avoid; check staleness before calling startLoad, or let the request signal that no loader pass is 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: bee53b4c6e

ℹ️ 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 +1042 to +1047
let key = CodexPricingSegmentKey(
day: row.day,
model: row.model,
timestampUnixMs: eraTimestamp,
pricingMode: row.pricingMode,
hasKnownCost: row.knownCostNanos != 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 Retain the resolved pricing model in cache segments

When a priority turn's displayed model is an alias but its completed response supplies the billable model, codexRowsWithPricingMetadata preserves that resolved value in row.pricingModel; this segment key and accumulator retain only row.model, and hydration later also sets pricingModel to that display model. For cases such as codex-auto-review resolved to gpt-5.4, the cache-first report therefore cannot reproduce the live priority price and may show no cost until the full scan completes. Include the resolved pricing model in the persisted segment and its grouping key.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Current-head repair and proof for PR #3107:

  • Head: 632219a64d695d94760514a3ab831a9469683f45
  • Addressed the latest exact review finding: aggregate-only cache segments now persist and group by the resolved billable pricingModel, and hydrate it instead of substituting the display alias.
  • Also closed the remaining TTL gap: a successful dashboard snapshot skips the loader for five minutes, so pane reopen does not rescan Codex accounts.
  • Local proof: 65 focused tests passed across 7 suites; make check reports 0 SwiftFormat files and 0 SwiftLint violations. Tests use isolated temporary stores and no provider credentials.
  • Current CI run: https://github.com/steipete/CodexBar/actions/runs/32885757159

@clawsweeper please re-review this exact head.

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Please review the current pushed head 632219a64d695d94760514a3ab831a9469683f45 (not the superseded bee53b4c6e head).

@clawsweeper

clawsweeper Bot commented Aug 25, 2026

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@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: 632219a64d

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

else { return }
if self.lastSuccessfulConfiguration != nil,
self.failedSourceCount == 0,
self.nowProvider().timeIntervalSince(self.loadedAt) < Self.dashboardSnapshotTTL

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 Track TTL freshness separately from the reporting clock

When the pane receives a same-day didBecomeActive notification, refreshDateWindow() advances loadedAt without fetching any data (lines 1571/1576). Comparing the TTL against that same field means app activations can make an old snapshot appear fresh; repeated activations within five minutes can postpone dashboard refresh indefinitely. Track the last successful load completion separately and use that timestamp for this TTL check.

Useful? React with 👍 / 👎.

Yuxin-Qiao commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Current-head update for PR #3107:

  • Head: e468f364cdc8962cd4e74f385fcac41bc6c699a9
  • Fixed the latest exact-review finding: dashboard snapshot TTL now uses a dedicated successful-load completion timestamp; refreshDateWindow() can no longer postpone expiry by rewriting the reporting-window loadedAt clock.
  • Final local proof: 65 focused tests passed across 7 suites, including repeated same-day activations at and after expiry, pricing-era/resolved-alias hydration, v1 migration, and OpenCodex window/race paths.
  • make check: 0 SwiftFormat files, 0 SwiftLint violations.
  • Current CI run: https://github.com/steipete/CodexBar/actions/runs/32889384161

@clawsweeper please re-review this exact head.

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Current head is e468f364cdc8962cd4e74f385fcac41bc6c699a9; please review this exact commit.

@clawsweeper

clawsweeper Bot commented Aug 25, 2026

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper

clawsweeper Bot commented Aug 25, 2026

Copy link
Copy Markdown

ClawSweeper status: review started.

I am starting a fresh review of this pull request: feat(spend): cache-first and 5m TTL for dashboard This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants