Skip to content

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

Open
irresi wants to merge 4 commits into
steipete:mainfrom
irresi:widget-token-row-age
Open

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

Conversation

@irresi

@irresi irresi commented Jul 7, 2026

Copy link
Copy Markdown

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 7, 2026, 3:21 PM ET / 19:21 UTC.

Summary
The PR adds token-specific freshness metadata for widget token rows, stale-age labels, cached native/Pi scan-time preservation, fast-failure token retry behavior, and focused regression tests.

Reproducibility: yes. The linked issue is source-reproducible and maintainer-confirmed: current main can persist a fresh widget snapshot before the slower token-cost refresh updates tokenSnapshots.

Review metrics: 2 noteworthy metrics.

  • Patch surface: 12 files, +414/-27. The change 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 successfully when tokenUsage.updatedAt is absent.

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 ClawSweeper repair job is needed because the current PR head has no actionable findings; normal required checks and maintainer merge review should gate landing.

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 the repository's required checks and maintainer merge review, keeping the optional snapshot timestamp contract and focused cache/widget coverage intact.

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

Yes. The linked issue is source-reproducible and maintainer-confirmed: current main can persist a fresh widget snapshot before the slower 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, and the latest head preserves cached native/Pi scan times.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The inspected screenshot directly shows the changed WidgetKit behavior: a fresh widget header while Today and 30d token rows display their own stale age.
  • add proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The inspected screenshot directly shows the changed WidgetKit behavior: a fresh widget header while Today and 30d token rows display their own stale age.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The inspected screenshot directly shows the changed WidgetKit behavior: a fresh widget header while Today and 30d token rows display their own stale age.

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): The inspected screenshot directly shows the changed WidgetKit behavior: a fresh widget header while Today and 30d token rows display their own stale age.
  • proof: sufficient: Contributor real behavior proof is sufficient. The inspected screenshot directly shows the changed WidgetKit behavior: a fresh widget header while Today and 30d token rows display their own stale age.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The inspected screenshot directly shows the changed WidgetKit behavior: a fresh widget header while Today and 30d token rows display their own stale age.
Evidence reviewed

What I checked:

Likely related people:

  • steipete: Current-main blame/log for the widget snapshot, token refresh, widget rendering, and cost-fetcher paths points to Peter Steinberger's v0.41.0 update, and he posted maintainer verification for this PR's root cause, repair shape, compatibility, and test coverage. (role: feature-history owner and recent verifier; confidence: high; commits: a83a83fa4131, fc27b17620e4; files: Sources/CodexBar/UsageStore.swift, Sources/CodexBar/UsageStore+WidgetSnapshot.swift, Sources/CodexBarWidget/CodexBarWidgetViews.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 (7 earlier review cycles)
  • reviewed 2026-07-07T08:52:34.129Z 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-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

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