Skip to content

fix: Give widget token rows their own updated time instead of inheriting snapshot freshness#1967

Merged
steipete merged 7 commits into
steipete:mainfrom
irresi:widget-token-row-age
Jul 9, 2026
Merged

fix: Give widget token rows their own updated time instead of inheriting snapshot freshness#1967
steipete merged 7 commits into
steipete:mainfrom
irresi:widget-token-row-age

Conversation

@irresi

@irresi irresi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • persist a token-specific updatedAt on WidgetSnapshot.TokenUsageSummary so the
    widget's cost rows (Today, 30d) carry their own freshness instead of inheriting
    the provider entry's (Widget token usage can stay stale while usage snapshot shows as recently updated #1964)
  • disclose the lag on the token section's row title (Today · 42 min. ago; compact
    metrics render Today cost · 42 min. ago) once the token snapshot trails the
    entry's freshness by more than 10 minutes; fresher rows render unchanged
  • clear the token-fetch TTL on fast failures so the next scheduled pass retries,
    while timed-out scans keep the TTL so a slow corpus cannot thrash back-to-back rescans
  • legacy snapshots without the new field decode as fresh, so existing widget data
    renders exactly as before

Fixes #1964.

Why a token-specific updatedAt and not coalescing the refresh

Regular refreshes schedule the token-cost refresh asynchronously and immediately
persist a widget snapshot that reads whatever tokenSnapshots currently holds, so
the snapshot's generatedAt and the entry's updatedAt go fresh while tokenUsage
still carries the previous scan. The issue triage offered two shapes: make snapshot
writes wait for the in-flight cost refresh, or persist a token-specific updated time.
This PR takes the second. Waiting/coalescing would couple every snapshot write to the
slowest cost scan (which can legitimately take minutes over a large corpus and is
exactly the case that times out), while the disclosure fix keeps provider scanners
and refresh scheduling untouched — the shape the triage review recommended.

Illustrative snapshot shape (same provider entry, before vs after):

# main — stale cost rows are indistinguishable from fresh ones
"updatedAt": "<fresh>", "tokenUsage": { "sessionCostUSD": …, "sessionLabel": "Today" }

# this branch — the token rows carry their own age
"updatedAt": "<fresh>", "tokenUsage": { …, "updatedAt": "<42 min earlier>" }
# widget renders: "Today · 42 min. ago"

The TTL change covers the second half of the report: when a cost scan fails fast,
lastTokenFetchAt used to block any retry for the remainder of the fetch TTL, so the
stale rows outlived the next scheduled passes too. Fast failures now clear the TTL;
timeouts keep it (tokenFetchFailureAllowsEarlyRetry).

Testing

  • swift test --filter "WidgetSnapshotTests|UsageStoreWidgetSnapshotTests|UsageStoreTokenRetryPolicyTests"
    18 tests in 4 suites passed, including the new
    token usage summary round trips updatedAt and tolerates legacy payloads,
    token usage staleness discloses only meaningful lag,
    widget snapshot carries token usage age separately from entry freshness, and
    timed out token scans keep the fetch TTL while fast failures retry early
  • make check — SwiftFormat and SwiftLint clean
  • swift build — clean
  • full make test has one failing suite on my machine (AdaptiveRefreshTimerTests,
    timing-sensitive); it fails identically on unmodified main with the same
    expectations, so it is unrelated to this change

@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 2:04 AM ET / 06:04 UTC.

Summary
The branch adds optional tokenUsage.updatedAt metadata, renders stale-age suffixes for widget token rows, adjusts fast-failure token-fetch TTL handling, preserves cached scan times, and adds regression tests.

Reproducibility: yes. The linked issue and current-main source show a regular widget snapshot can be persisted before the asynchronous token-cost refresh updates tokenSnapshots.

Review metrics: 3 noteworthy metrics.

  • Patch surface: 12 files changed, +470/-27. The fix spans widget snapshot schema, widget rendering, token retry/cache logic, tests, and one release note entry.
  • Snapshot schema change: 1 optional field added. Compatibility depends on legacy widget snapshots decoding correctly when tokenUsage.updatedAt is absent, which the PR covers.
  • Proof artifact: 1 screenshot inspected. The screenshot directly shows the user-visible widget freshness disclosure that the PR changes.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #1964
Summary: This PR is the candidate fix for the linked widget token freshness issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • No automated repair is needed; the current PR head has no actionable findings and should proceed through maintainer merge review.

Security
Cleared: No security or supply-chain concern was found; the diff does not change dependencies, workflows, scripts, permissions, secrets handling, or downloaded artifact execution.

Review details

Best possible solution:

Land this PR after required checks and maintainer merge review, keeping the optional snapshot timestamp contract and focused cache/widget regression coverage intact.

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

Yes. The linked issue and current-main source show a regular widget snapshot can be persisted before the asynchronous token-cost refresh updates tokenSnapshots.

Is this the best way to solve the issue?

Yes. Persisting token-specific updatedAt and disclosing stale token row age is a narrow fix that avoids coupling every widget snapshot write to slow token-cost scans while preserving legacy snapshot behavior.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a normal-priority user-visible widget freshness bug fix with limited blast radius and no setup or runtime blocker.
  • 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 (screenshot): Screenshot proof shows a fresh widget header while Today and 30d token rows display their own stale age in a local branch build.
  • proof: sufficient: Contributor real behavior proof is sufficient. Screenshot proof shows a fresh widget header while Today and 30d token rows display their own stale age in a local branch build.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. Screenshot proof shows a fresh widget header while Today and 30d token rows display their own stale age in a local branch build.
Evidence reviewed

What I checked:

Likely related people:

  • steipete: Current-main blame owns the affected widget snapshot and token-cache lines through the v0.41.0 import, targeted history shows earlier widget persistence and idle cost-scan work by Peter Steinberger, and he posted maintainer verification plus the latest test-strengthening commits on this PR. (role: feature-history owner and recent verifier; confidence: high; commits: 98fd481366c4, 8cbe84873c52, a83a83fa4131; files: Sources/CodexBar/UsageStore.swift, Sources/CodexBar/UsageStore+WidgetSnapshot.swift, Sources/CodexBarCore/WidgetSnapshot.swift)
  • Nimrod Gutman: Symbol history shows Nimrod Gutman introduced the Pi session usage merge path whose cached scan time this PR preserves. (role: adjacent feature introducer; confidence: medium; commits: c23edb9dc048; files: Sources/CodexBarCore/PiSessionCostScanner.swift, Sources/CodexBarCore/CostUsageFetcher.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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.

How this review workflow works
  • 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.
Review history (9 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-07T08:58:56.018Z sha 24c60f0 :: needs real behavior proof before merge. :: [P2] Apply stale age in the switcher small widget | [P2] Suffix the 30d token rows when token data is stale
  • reviewed 2026-07-07T11:09:23.113Z sha fc27b17 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-07T16:25:52.222Z sha fc27b17 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-07T18:05:21.886Z sha 97ce6c7 :: needs changes before merge. :: [P2] Preserve cached Pi scan time
  • reviewed 2026-07-07T18:11:34.432Z sha 97ce6c7 :: needs changes before merge. :: [P2] Preserve cached Pi scan time
  • reviewed 2026-07-07T19:15:36.369Z sha 412750a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-07T19:21:12.338Z sha 412750a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T05:08:22.825Z sha dca72fb :: needs maintainer review before merge. :: none

@irresi irresi changed the title Give widget token rows their own updated time instead of inheriting snapshot freshness fix: Give widget token rows their own updated time instead of inheriting snapshot freshness Jul 7, 2026
@clawsweeper clawsweeper Bot added 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. P2 Normal priority bug or improvement with limited blast radius. labels Jul 7, 2026
irresi and others added 2 commits July 7, 2026 03:37
…hot freshness

Token-cost rows refresh on a slower cadence than quota rows, so a fresh
widget snapshot could present hour-old token data as current
(steipete#1964). Persist a token-specific updatedAt on
WidgetSnapshot.TokenUsageSummary, suffix widget row titles with the token
snapshot's own age once it lags the entry's freshness signal past ten
minutes, and clear the token-fetch TTL on fast failures so the next
scheduled pass retries instead of serving stale rows for the rest of the
TTL. Legacy snapshots without the new field decode as fresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@steipete steipete force-pushed the widget-token-row-age branch from 24c60f0 to fc27b17 Compare July 7, 2026 11:05
@steipete

steipete commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Maintainer verification on exact head fc27b17620e4c0817fc3793a2bae01a10c682b9b:

  • Public Model Identifier Gate: PASS.
  • Root cause confirmed: regular quota refreshes can persist a fresh provider/widget timestamp before the slower asynchronous token-cost scan updates tokenSnapshots.
  • Repair: persist token-specific updatedAt, disclose meaningful lag on every Today/30d token row (including switcher-small and compact metrics), and allow fast scan failures to retry before the hourly TTL while retaining timeout throttling.
  • Compatibility: legacy snapshots without the optional token timestamp decode successfully and keep their prior presentation until the app writes a current snapshot.
  • Focused: 62 tests passed across widget snapshot schema, app projection, retry policy, and widget formatting.
  • Quality: make check passed (SwiftFormat clean; SwiftLint 0 violations).
  • Full suite: make test passed all 49 shards / 585 selections in 281s.
  • Autoreview: clean, no accepted/actionable findings (0.98 confidence).
  • Review: zero unresolved threads; no dependency changes.
  • Packaged proof setup: Developer ID-signed app and widget extension verified and installed on macOS 26.5 (25F71) with a secret-free synthetic stale-token snapshot prepared. The VM is currently locked at the login screen, so final WidgetKit rendering proof remains pending; no screenshot has been uploaded.
  • Exact-head CI: changes, lint, security, Linux arm64, and Linux x64 passed. Hosted macOS shards remained capacity-queued; local macOS coverage is complete.

Patch surface: 9 files, +232/-15. Merge remains blocked only on real WidgetKit rendering proof (or an explicit proof waiver).

@irresi irresi marked this pull request as ready for review July 7, 2026 16:22

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread Sources/CodexBar/UsageStore+WidgetSnapshot.swift
@irresi

irresi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Add Screenshot Proof
image
Header fresh, token row disclosing its own age
branch build running locally on macOS 26.5.

Hydrating from the persisted codex scan cache rebuilt the token snapshot
with the hydration time as updatedAt while returning the real cache scan
time separately, so a stale cached token row could inherit app-start
freshness and skip the widget's stale-age suffix for the rest of the
fetch TTL. Cached snapshots now carry the cache's lastScan time; paths
without a known scan time keep the previous behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@irresi

irresi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 7, 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 repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. and removed 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 Jul 7, 2026
Merged cached codex snapshots dropped the known scan times entirely, so
a hydrated snapshot that included cached pi sessions fell back to the
hydration time as updatedAt. Expose the pi cache's lastScan time and use
the oldest constituent scan as the merged snapshot's updatedAt, while
keeping TTL bookkeeping native-only so merged loads never suppress a
pending rescan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@irresi irresi force-pushed the widget-token-row-age branch from 02225b5 to 412750a Compare July 7, 2026 19:10
@clawsweeper clawsweeper Bot removed proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jul 7, 2026
@irresi

irresi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 7, 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 repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. 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. labels Jul 7, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Maintainer proof for exact head 054743ab969f0785b2a6a5b2e988c403673c1e02:

  • Deep review: production freshness path, cache merge semantics, legacy decoding, every widget presentation, and retry policy reviewed; no accepted/actionable autoreview findings (confidence 0.96).
  • Strengthened regression coverage: distinct native/Pi scan times, Pi-only TTL behavior, native-only fallback, and compact Today/30d stale labels.
  • Focused: swift test --filter 'WidgetSnapshotTests|UsageStoreWidgetSnapshotTests|UsageStoreTokenRetryPolicyTests|CodexBarWidgetProviderTests|CostUsageFetcherCacheSnapshotTests' — 73 tests passed.
  • Static gates: make check — SwiftFormat clean; SwiftLint 0 violations.
  • Full suite: make test — all 49 shards passed.
  • Package: exact source built as com.steipete.codexbar.debug; app and widget extension signed by Developer ID Application Peter Steinberger (Y5PE65HELJ); codesign --verify --deep --strict and spctl accepted. App binary SHA-256: 5eb210a51bc01e4056d6c5deac586e0afadae713bd83bae226c5b6cd966c7b96.
  • Live WidgetKit proof on macOS 27.0 beta (26A5368g), using a secret-free debug app-group fixture: a 45-minute token/quota lag rendered Today · 48 min. ago and 30d · 48 min. ago; moving token freshness within five minutes removed both suffixes; omitting the token timestamp (legacy payload) also rendered without a suffix. The medium widget was added to the desktop and refreshed from stale to fresh. Local screenshots retained; no account identity or credential data used.
  • Public Model Identifier Gate: PASS. No public model identifiers added or changed.
  • Dependencies: unchanged.
  • Exact-head CI: run 28997516994 passed lint, Linux x64/arm64, all four macOS shards, aggregate, and GitGuardian.

Landed as squash merge 69073287919ff4ec63b29b3b16c36056d04ce86e; linked issue #1964 is closed.

@steipete steipete merged commit 6907328 into steipete:main Jul 9, 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: 📸 screenshot Contributor real behavior proof includes screenshot evidence. 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.

Widget token usage can stay stale while usage snapshot shows as recently updated

2 participants