Handle Codex reset credits omitted after redemption - #2728
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 424b798c0e
ℹ️ 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".
|
Codex review: needs real behavior proof before merge. Reviewed August 7, 2026, 2:59 PM ET / 18:59 UTC. ClawSweeper reviewWhat this changesThis PR lets CodexBar refresh quota after a manual reset when two later reset-credit inventories omit the formerly available credit and both report a lower available-credit count. Merge readiness⛔ Blocked until stronger real behavior proof is added - 4 items remain Keep open for an explicit proof-sufficiency decision: the focused change appears correct and bounded, but the exact-head live smoke did not exercise the new two-sample omission path through quota publication. Priority: P2 Review scores
Verification
How this fits togetherCodexBar polls Codex OAuth usage and reset-credit inventory to update the menu-bar quota display. Its confirmation layer compares prior, initial, and confirmation snapshots before accepting an early weekly reset. flowchart LR
A[Codex OAuth usage] --> C[Usage snapshots]
B[Reset-credit inventory] --> C
C --> D[Early-reset confirmation]
D --> E{Two samples corroborate consumption?}
E -->|Yes| F[Publish refreshed quota]
E -->|No| G[Preserve prior quota]
Decision needed
Why: Only a maintainer can waive the external-PR real-behavior gate when the one-time provider event cannot presently be repeated safely. Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Land after a redacted production-boundary trace proves the available-to-omitted transition publishes the refreshed quota, or after an explicit maintainer decision accepts the supplied trace, replay, and smoke as sufficient proof. Do we have a high-confidence way to reproduce the issue? Yes in source: current main only accepts a retained redeemed credit, while the supplied real provider shape omits it; constructing those three snapshots reaches the guarded confirmation path. The reported focused replay was not independently run in this read-only review. Is this the best way to solve the issue? Yes for the code path: requiring two monotonic inventories, a lower count, an unexpired prior credit, and the existing boundary checks is a narrow way to recognize the observed provider representation. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 22b24b885693. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (9 earlier review cycles; latest 8 shown)
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Proof follow-up for maintainer judgment: I built and launched the exact PR head This is an after-fix real-provider smoke run, but it cannot reproduce the one-time Please use maintainer judgment on whether this combined evidence is sufficient, or whether the PR should remain open until another safe live redemption becomes available. |
|
Landed. Verification before merge (Codex-gathered evidence, maintainer-reviewed):
Thanks @endless7 — two clean landings in one week on this subsystem. |
* fix: use in-process libproc instead of ps/lsof scans (#2599) Replace repeated macOS process-inspection subprocesses with scoped libproc and sysctl calls while preserving the Linux ps, lsof, and /proc paths. Add defensive PROCARGS2 parsing and self-process integration coverage to harden #2267. Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: size provider menus to their content (#2602) * build: refresh widget dependency lockfile (#2603) * build: sync widget Package.resolved with root (SweetCookieKit 0.5.1) (#2604) Root Package.resolved moved ahead of the widget workspace copy, so Scripts/package_app.sh release failed at the widget step with an out-of-date resolved file error. The script's failure handling was verified correct (non-zero exit, no stale bundle produced). Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * refactor: derive provider registrations from descriptors (#2606) * refactor: consolidate provider bootstrap manifests * refactor: derive provider icon styles from descriptors * refactor: derive provider ancillary registrations * refactor: derive widget provider metadata * test: enforce provider widget metadata sync * docs: simplify provider authoring workflow --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: make iCloud sync survive first contact with a real fleet (#2608) Four fixes from live multi-Mac testing: - CKRecord rebase crash: allKeys() includes encrypted field names; routing them through the plain subscript threw NSInvalidArgumentException and crash-looped every receiver applying fetched records (regression test). - Fetch-before-push on first sync: a fresh device now applies fleet state before composing pushes, so its editCount-1 records can't win conflict ties and clobber the fleet. - Persist remote applies: applyExternalConfig skips disk writes by design (reload path); sync applies now schedulePersistConfig so config.json, the CLI, and the next launch see the merged result. - Dirty-set push gating: startup no longer wholesale-uploads local config (a relaunched stale Mac degraded the richest Mac's config twice in testing). Providers push only when locally edited (persisted dirty set, cleared on save success), with an empty-fleet bootstrap seeding path. Verified live: three-Mac convergence (golden profile incl. E2E-encrypted secrets propagated to two receivers; source unchanged; fleet devices and snapshots visible everywhere; no-account Mac degrades gracefully). Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(alibaba-token-plan): personal requests rejected with Workspace.NotAuthorised (hardcoded switchAgent) (#2533) * fix(alibaba-token-plan): personal requests rejected with Workspace.NotAuthorised The Personal/Solo path hardcoded `switchAgent: 1_233_135` in the `cornerstoneParam` request body. The gateway binds that value to a specific account's workspace, so for any other account the call is rejected with `BailianGateway.Workspace.NotAuthorised` even though the outer envelope claims `code: "200"`. Omitting the field lets the gateway resolve the session's default workspace. Also: - Resolve `sec_token` best-effort for personal requests (the browser always sends it; some accounts are rejected without it) and append it to the body when available, mirroring the Teams path. - `throwIfErrorPayload` now reads `errorCode`/`errorMsg` from the nested frame that carries `success: false`, so the real gateway error is surfaced instead of a misleading "API error: 200". Authorization-family errors map to `invalidCredentials` so the UI prompts for re-authentication. Fixes #2500. Verified live against the mainland Personal/Solo API and covered by new regression tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: preserve Alibaba personal sessions --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Add Notion AI provider (#2552) * Add Notion AI provider Tracks the two usage-allowance windows Notion shows in Settings > Notion AI > Usage: the rolling 6-hour window and the billing-period window. Notion begins enforcing the allowance on 2026-08-03; before then the same endpoint already returns real numbers with "enforcement": "preview". Reads two cookie-authenticated endpoints on app.notion.com that the Notion web app itself calls: getSpaces for account identity and workspace plans, and getCreditRateLimitStatus for the allowance. Only Business and Enterprise workspaces carry one; anything else reports not_applicable as a clear error rather than an empty gauge. The imported cookie header is persisted through CookieHeaderCache. Chromium cookie reads are gated to user-initiated refreshes to avoid a Keychain prompt, so without a cached header the provider fails on every background refresh and reports "no cookies found" while the session is valid. Cookies are also de-duplicated by name across the Notion domains, since a stale token_v2 left on the legacy notion.so alongside the live one would otherwise send both. Every field of the rate-limit response is optional, so an unrelated 200 body decodes cleanly into an all-nil status. The parser rejects a payload carrying neither window, and a window with no usable limit is omitted rather than reported as 0% used. Adding the .notion debug-log case tipped UsageStore.debugLogText past the cyclomatic-complexity cap, so .openai and .azureopenai are folded into one case; both already call the same apiKeyDebugLine helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(notion): harden cookie session handling * test: isolate widget snapshot persistence * feat(notion): pace both allowance bars and honor CLI provider settings The billing-period window is scored against the real calendar month ending at its reset rather than a flat 30 days, and the rolling window gets the session-pace treatment on both the card and in `codexbar usage`. Notion reports only `periodEndMs` for the billing period, so the snapshot carries the shared monthly sentinel and the descriptor declares `.calendarMonthResetWindow`. Resolution then substitutes the true cycle length. Three paths scored these windows without resolving first -- `UsageStore.weeklyPace` (menu-bar pace token, "runs out" text, predictive pace warnings) and `resetWindowPaceDetail` when handed a precomputed pace -- so a February cycle read 6% expected at 0% used and a 31-day cycle lost its pace token for a day. Both now resolve, which also fixes the eight other providers that carry the same sentinel. The CLI ignored the Notion settings snapshot entirely, so Workspace ID, a manual cookie header, and the `off` source had no effect on `codexbar`. Also drops a rolling window length that parses to exactly the monthly sentinel (`30d`, `720h`, `43200m`), which would otherwise be resolved as a calendar cycle ending hours from now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * release: 0.47.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: make widget ProviderChoice display metadata a static literal The AppIntents metadata processor in current Xcode rejects computed caseDisplayRepresentations (must be a literal, exhaustive dictionary), which broke the 0.47.0 release build. Titles are pinned to the descriptor registry by WidgetProviderChoiceTests so providers cannot drift silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: update appcast for 0.47.0 * docs: open 0.47.1 Unreleased Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add scoped-weekly (Fable) percentage as a menu-bar layout token (#2440) * Add scoped-weekly (Fable) percentage as a menu-bar layout token Claude's model-scoped weekly carve-outs (the "Fable only" window from #1851) already show in the dropdown and in quota notifications, but the always-visible menu bar could only show session, weekly, or automatic. This adds a scoped-weekly token so the promo-window limit can sit in the bar. - New PercentWindow.scopedWeekly, shown with an "F" prefix, selectable in the layout editor (palette, label, live and representative previews). - scopedWeeklyWindow resolver matches the claude-weekly-scoped-* id prefix rather than a model name; when several are active it shows the most constrained one. - The scoped-weekly percent is added to the icon-observation signature so a scoped-only change refreshes the title, mirroring weekly= and avoiding the stale-title problem from #2300 and #2299. - Absent window renders the standard "–" placeholder; editor label localized. Addresses #2360. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review: label scoped-weekly token by active model Codex flagged that the token hard-coded "F" / "Fable only" while the resolver accepts any claude-weekly-scoped-* window and picks the most constrained, so a non-Fable window could be shown under the wrong model label. - scopedWeeklyNamedWindow now returns the NamedRateWindow. - The .scopedWeekly token derives its prefix from the active window's title (first letter) and its accessibility text from the full title, instead of a fixed "F" / "Fable only". - Carry the title through MenuBarLayoutRenderData and include it in the icon-observation signature, so a model change refreshes the title. - Resolver test now asserts a non-Fable most-constrained window carries its title. The editor palette label stays "Fable %" (a proper noun that reads the same across locales); a generic name is left as a naming choice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add menu bar pace layout tokens The 0.45 layout editor replaced the old Percent/Pace/Both display modes, but no token exposes the signed pace delta the Both mode used to render. `Runs out` is not a substitute: it answers when a window ends, always estimating from the weekly (or automatic) lane, while pace answers how far off the sustainable rate usage currently runs. Add Session/Weekly/Auto pace tokens that mirror the percent tokens' window selection and reuse the existing `MenuBarDisplayText.paceText` formatting (`+11%` ahead of the rate, `-8%` behind, `0%` on pace). Each token resolves pace for its own window, so Weekly pace never borrows the session delta. Pace needs the store's historical dataset and work-day setting, so it is resolved upstream like `runsOut` through a shared `menuBarLayoutPaceText` helper that both the status item and the editor preview call. The existing 3% expected-usage floor in `weeklyPace` still applies, so a token renders the en-dash placeholder early in a window while its siblings stay visible. Refs #2534 * Include layout pace tokens in the icon observation signature Pace values change with the historical dataset, the work-day setting, and the clock, none of which move the percent fields already hashed by providerStoreIconObservationSignature. A historicalPaceRevision bump therefore woke the icon observer but produced an unchanged signature, so updateIcons() was skipped and a custom pace token kept its stale value until an unrelated icon change forced a redraw. Contribute the active layout's pace values to the signature the same way cost and account tokens already do, gated on the layout actually containing a pace token. The regression test renders two snapshots with identical used percents but different resets: without this fix both signatures were identical. Refs #2534 * docs: credit menu bar pace contributor * fix: localize scoped weekly label --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Petr Kratochvíl <krato@krato.cz> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Add one-shot dashboard snapshot command (#2499) * Add one-shot dashboard snapshot command * Fix dashboard pricing refresh policy * Honor Cursor source policy in dashboard * test: dedupe cliExecutableURL helper after branch update Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: JS provider plugin runtime prototype (behind debug flag) (#2617) * feat: add provider plugin runtime * feat: convert synthetic venice and crof to JS plugins * test: prove JS provider parity * docs: document provider plugin prototype * fix: satisfy plugin runtime lint checks --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: sync CLI config edits via iCloud (#2618) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: make widget snapshot storage hermetic (#2619) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat(menu): compact usage detail rows (#2620) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Shun Min Chang <ji394m6y7@gmail.com> * feat: add China Kimi and GLM quota routing (#2621) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: haoli <haoli@local.dev> * feat: enrich Kimi monthly usage from desktop (#2622) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: haoli <haoli@local.dev> * feat: declarative provider detail sections + JS detail providers (#2624) * feat: add declarative provider detail model * feat: render declarative provider details * feat: bridge provider details from JavaScript * feat: convert detail providers to JavaScript * style: satisfy provider detail test lint * fix: align z.ai plugin quota lanes --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Unify pi and OMP agent sessions (#2626) * Add live OhMyPi agent session discovery Detect running OhMyPi harness processes, correlate them with bounded session metadata, and expose normalized sessions through the existing CLI and menu paths. * Address OhMyPi review correlation gaps Resolve OhMyPi metadata per live process environment, reject stale records, and isolate directory scan budget so existing provider discovery remains intact. Add focused regression coverage and document the fail-closed behavior. * Preserve existing session metadata during OhMyPi scans Give OhMyPi its own bounded directory budget so Codex and Claude correlation remain available, with regression coverage for the shared-provider behavior. * Preserve legacy session JSON compatibility Add an explicit v2 session payload for OhMyPi while keeping legacy remote clients decodable, and document and test the negotiated fallback. * Expose OhMyPi in local session JSON Keep the documented local --json output complete while preserving the remote v2-first compatibility fallback for older installations. Update CLI help, focused protocol coverage, and session documentation to distinguish current local output from legacy remote responses. * fix(sessions): preserve legacy JSON compatibility * Correct session JSON protocol overview * feat(sessions): unify Pi-family discovery Co-authored-by: William Mitchell <wdmitchell.uk@gmail.com> --------- Co-authored-by: William Mitchell <wdmitchell.uk@gmail.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix(zoommate): preserve browser cookie scope (#2627) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Keep cached spend visible while Codex refreshes (#2628) * fix: show cached spend while refreshing Co-authored-by: hhh2210 <hzy2210@gmail.com> * test: add cached spend refresh proof --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: hhh2210 <hzy2210@gmail.com> * feat: route GLM credentials by region (#2623) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: haoli <haoli@local.dev> * Define Codex Fast cost as API Fast USD (#2632) * Centralize Codex priority pricing * fix: define Codex Fast USD pricing * test: split pricing expectation math for CI type-checker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Bryan Font <bfont@me.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix: parse Command Code usage windows (#2630) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Derek Zeng <zengzhuoxi@gmail.com> * feat: plugin cookie capability and host API extensions * feat: extend plugin host and convert providers * fix: align z.ai plugin credential routing --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: introduce ProviderInstanceID identity seam (#2636) * test: characterize provider instance identity * feat: add provider instance identity seam --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat: user-installed provider plugins with TypeScript support (#2642) * feat: add user-installed provider plugins * fix: broker owns plugin HTTP representation headers Apply broker-owned Accept, Accept-Encoding, and Content-Type after plugin-supplied headers so a plugin cannot relax the user-plugin response boundary. Test transport gains configurable response headers. * build: add reproducible sucrase bundle verification script Regenerates and verifies Sources/CodexBarCore/Resources/Plugins/ sucrase-3.35.1.min.js from the official npm artifact (sucrase@3.35.1, esbuild@0.25.8 pinned, IIFE browser bundle). Expected SHA-256 4d997e15b72cbc9ccf6e743c30c6eb48bf4533f6709852367b40766be5eba70b was independently reproduced by the coordinator from the npm registry; 'check' mode fails closed on any mismatch. * fix: gate user-plugin registry lookup for non-JavaScriptCore platforms Linux CLI builds compile CodexBarConfig without the plugin runtime; unknown plugin config entries are dropped with the existing warning, matching the documented macOS-only plugin boundary. --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: deflake Kimi grace budget timing (#2657) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: keep widget-snapshot container I/O out of test runs (#2656) * test: keep widget-snapshot container I/O out of test runs AdaptiveRefreshHeuristicsTests (and any suite reaching persistWidgetSnapshot without a save override) could hang forever on macOS 26 hosts: WidgetSnapshotStore.load() opens the real app-group container file and the open() can block indefinitely behind app-data (TCC) gating. Gate persistWidgetSnapshot so test runs only persist when a test explicitly installs _test_widgetSnapshotSaveOverride, and pin the behavior with regression tests. * test: open widget-snapshot gate for injected snapshot URLs Main's plugin series added a widgetSnapshotURL injection seam that promotion tests rely on; the gate now admits it alongside the save override and replaces the duplicate inline DEBUG guard. --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat: add shared-cache token activity heatmap (#2625) * feat: add standalone token activity heatmap * fix: preserve token activity coverage * fix: complete activity heatmap navigation * fix: expose daily activity accessibility * fix: publish daily accessibility semantics * fix: expose dates in activity accessibility * fix: expose weekly activity accessibility * feat: derive token activity from shared cache --------- Co-authored-by: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * Expose Codex scan completeness in cost JSON (#2520) * Expose provider-native cost scans * feat(cli): expose Codex cost scan completeness Co-authored-by: NickGuAI <yu.gu.columbia@gmail.com> --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: refresh Plugins pane visuals to match sibling settings panes (#2662) * feat: resizable settings sidebar with persisted width (#2660) * feat: resizable settings sidebar with persisted width Replace the fixed 260pt settings sidebar with a drag-resizable one (200-380pt, persisted via AppStorage). The divider stays the original SwiftUI hairline; an input-only AppKit strip overlaid on the detail pane's leading edge handles dragging, so the HStack layout is untouched. Hover cursor via .pointerStyle(.columnResize) on macOS 15+ plus an explicit-rect tracking area (visibleRect spans the whole window under NSHostingView, so .inVisibleRect never fired). * fix: hold the sidebar resize cursor with a cursor rect The hover cursor was set imperatively on mouse enter/move, which SwiftUI undid on its next render — the settings panes re-render continuously, so the resize cursor only flashed. Cursor rects are declarative: AppKit re-asks the view on every invalidation, so the cursor holds. Measured on a probe harness hosting this view under a 20Hz re-render loop, sampling NSCursor.currentSystem at 25Hz while an external driver moved the pointer: imperative-only gave 0% resize on hover and 40% during drag; with cursor rects, two clean transitions (arrow -> resize on entry, back on exit) and 100% held across hover and drag. * style: fix SwiftFormat violations in SidebarResizeHandle Mechanical ./Scripts/lint.sh format output; #2660 landed with docComments and wrap-body violations that broke CI on main. * Wait for OpenCode Go Zen balance in CLI usage reads (#2583) * Wait for OpenCode Go Zen balance in CLI usage reads * fix: bound OpenCode Go CLI balance lookup * Keep OpenCode Go Zen wait scoped to usage reads * Measure OpenCode Go Zen wait from task start --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * OpenCode Go: per-model cost breakdown by day (#2649) * feat: per-model cost breakdown for OpenCode Go's daily cost history Extract each local assistant message's modelID from opencode.db (the real model behind the constant opencode-go Zen-proxy providerID) and group cost/request counts by (day, model) instead of just by day, so the shared Cost history chart shows a per-model breakdown for OpenCode Go the same way it already does for Claude/Codex. Rows with no modelID fall back to an "unknown" bucket instead of being dropped. * fix: use lowercase docs/claude.md link to pass case-sensitive CI lint macOS resolves docs/CLAUDE.md to the same file as docs/claude.md, but the Linux lint runner's case-sensitive filesystem doesn't, failing the documentation-link check. * fix: normalize the trimmed model id when grouping OpenCode Go costs The grouping key used the untrimmed modelID even though emptiness was checked against the trimmed value, so a model id with incidental leading/trailing whitespace would form its own bucket instead of merging with the clean value for the same model. Group by the trimmed value consistently, and add regression tests for whitespace-only and whitespace-padded model ids. * Fix ETXTBSY race in claude-swap CLI card test (#2644) * Fix ETXTBSY race in claude-swap CLI card test The test wrote a shell script, set mode 0755, and executed it immediately. Under swift test --parallel a concurrent fork inherits the still-open write descriptor, so execve fails with ETXTBSY and the launch reports Cocoa 256. Write the script body as data and execute a checked-in trampoline that reads it, so execve only ever touches a file no test process has written. Measured in swift:6.3.3-noble on arm64: 112 failures in 600 attempts with the old shape, 0 in 600 with the trampoline. * Apply ETXTBSY fix to the Linux test target The first commit only touched Tests/CodexBarTests, which Package.swift builds on macOS alone. The Linux job compiles CodexBarLinuxTests from TestsLinux, so the race remained in the target that actually flaked. Convert all three TestsLinux sites through a shared FakeExecutable helper, and keep the macOS mirror in step. Verified on Linux arm64 in swift:6.3.3-noble: 355 tests in 53 suites pass. * Pin Perplexity promo expiry formatter to en_US_POSIX (#2651) * Pin Perplexity promo expiry formatter to en_US_POSIX The formatter set dateFormat but not locale, so MMM rendered in the user's language inside an otherwise English string and diverged from the JS plugin projection. Matches the en_US_POSIX pinning already used by every other provider formatter. Refs #868 * Assert the Perplexity promo expiry locale pin in tests Parity coverage only trips on a non-English host, so an English runner passes both before and after the regression. Add assertions that hold on every host: the formatter keeps its en_US_POSIX locale, and the rendered month stays ASCII English. * Move the promo expiry fixture off the UTC month boundary The formatter keeps the host time zone, so a midnight-UTC January instant renders as Dec 31 west of UTC and failed the month assertion there even with the locale pin correct. * Decode Copilot credits_used for token-billed seats (#2593) (#2613) * Decode Copilot token-billing credits used * Cover credits used counter in token-billing card regression * Keep Copilot credits counter accessible through snapshots * Preserve Copilot credits counter across quota fallback * Expose Copilot credits counter in diagnostics * Keep credits counter on zero entitlement quota fallback * fix: keep Codex fork catch-up progress across appends (#2648) * fix: keep Codex fork catch-up progress across appends * Keep subagent fork appends on full rescan * Preserve zero-work subagent retries * Report unreadable Claude OAuth refresh as terminal (#2650) * Report unreadable Claude OAuth refresh as terminal When the delegated Claude CLI touch completes but the Keychain fingerprint does not move, the attempt reported a retryable failure. With foreign Keychain reads disabled in release and no credentials file for the profile, that state cannot be retried out of: the refreshed credential is unreadable no matter how often the touch runs. Add Outcome.unreadableAfterRefresh for that case, back off on the long cooldown instead of the short one, and drop the 'run claude login, then retry' advice that cannot help. The touch still runs first, so older Claude Code versions that write the credentials file keep recovering. Refs #2634 * Keep the delegated refresh Outcome contract public-stable CodexBarCore exports Outcome through its library product, so the added case would break downstream exhaustive switches on source update. Move the unreadable verdict onto an internal AttemptResult alongside the outcome and restore the original public cases; the fetcher reads the detail through attemptDetailed. * Cover retryable touch failure in unreadable configuration The touch-error path staying retryable (short cooldown, no terminal verdict) is deliberate: the error may be transient, and on older Claude Code a retried touch can still create the credentials file. Document that at the decision site and lock it in with a regression test so the SessionError variant from #2634 has explicit coverage. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * test: stabilize PTY overflow proof (#2664) * Classify Codex rate windows by duration (5h/weekly/30-day) (#2600) * Classify Codex rate windows by duration * Keep monthly Codex history visible in the chart * Suppress session pace for non-session Codex windows * Preserve monthly Codex windows in reset backfill and keep fallback session pace - Reset backfill rebuilt raw slots only from the session/weekly lanes, so a 43,200-minute primary (now classified .monthly) was dropped or overwritten by a stale cached session window whenever a trusted backfill baseline existed. Preserve monthly-classified slot windows in place and backfill their reset from a matching cached monthly window. - The session-pace guard rejected every non-300-minute Codex window, removing existing pace for fallback session durations (e.g. 540 minutes or nil). Suppress pace only for windows that classify into the weekly/monthly lanes, and let the central guard drive the card instead of exact-duration gates. - Move the Codex reset-backfill helpers into their own file (file-length lint). - Regression tests for both paths; they fail without the fixes. * Retain monthly reset baselines in codex backfill merge --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * Retain priced Codex models when Auto Review is unpriced (#2643) * Retain partial Codex model rows * Use safe Codex dashboard test fixture * Fail closed for malformed Codex model costs * Add regression test for fully unpriced Codex routing history * Extract model breakdown helpers to satisfy type body length --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Peter Steinberger <steipete@gmail.com> * Bound Codex cost cache persistence size (#2637) (#2646) * Bound Codex cost cache persistence size * Preserve resumable and fork-dependent cache entries * Prune cache against requested window * Prune candidate artifacts over byte budget * Scope cache guards and preserve active sessions * Keep cache loadable under byte budget * Mark trimmed caches for catch-up and prune discovery * Bound sole oversized entries and reset discovery cursors * Mark stripped entries incomplete * Preserve full report and live fork protection during trim * Compact protected fork parents over budget * Enforce byte cap after underestimated encodes * Exclude lineage-only parents from fork protection * Re-encode after forced prune and bound lookback state * Use report window for catch-up and exclude lineage parents * Compact parents required by kept trim survivors * Prune orphaned discovery mappings under budget * Preserve lookback work and align report bounds * fix: lower cost-cache load cap and never persist a refusable artifact The 1 GiB load cap still let a 256 MiB-1 GiB legacy artifact be decoded in one shot, which is exactly the MALLOC_LARGE multi-GiB spike #2637 traced. Since save now bounds Codex artifacts to 256 MiB, anything meaningfully above the budget is legacy and cheaper to rebuild bounded; drop the cap to 320 MiB (budget + enforcement slack). Also make save uphold the loader's contract: when budget enforcement cannot shrink the payload below the load cap (unstrippable resume or buffered state), remove the artifact instead of writing one that every launch would decode just to refuse. * Preserve recursive lookback roots and share ID capacity * Keep legacy lookback roots in the scan queue * Preserve complete report across repeated trims --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for triage merge batch (#2649, #2613, #2600, #2646, #2648, #2650, #2643, #2651) * Use OpenRouter server remaining for key limit meter (#2612) * Use OpenRouter server remaining for key limit meter * Treat negative OpenRouter remaining as exhausted quota * Align JS OpenRouter meter with Swift quota path * Clamp JS OpenRouter server remaining to key limit Match the Swift quota path's inclusive [0, keyLimit] clamp so a server remaining above the configured limit renders 0% used instead of suppressing the meter. Adds an above-limit Swift/JS parity fixture. * Validate selected OpenRouter fallback quota value --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for OpenRouter key-limit fix (#2612) * build: sync widget-extension Package.resolved to SweetCookieKit 0.5.2 The root package moved to SweetCookieKit 0.5.2 but the widget project's pinned resolved file lagged at 0.5.1, breaking Scripts/package_app.sh (xcodebuild runs with automatic resolution disabled). CI does not build the widget xcodeproj, so this only surfaced in local packaging. * feat: localize Plugins pane (#2663) * feat: localize Plugins pane * docs: note Plugins pane localizations * refactor: migrate provider payloads onto declarative details model (#2658) * refactor: migrate provider details batch one * refactor: migrate provider details batch two * refactor: migrate provider details batch three * fix: preserve provider detail rendering parity * fix: migrate Copilot credits into provider details * fix: pin Poe details to UTC Poe timestamps are UTC and the daily buckets already used it, but the recent-activity labels and Today bucket used the local zone in both the Swift and JS paths, flaking the golden on non-Pacific runners. Golden proven invariant under TZ=UTC, America/Los_Angeles, Asia/Tokyo. * fix: normalize OpenRouter reset detail * test: pin Poe menu fixture to UTC * fix: preserve OpenRouter key details --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: decompose provider settings snapshot into provider-owned sections (#2659) * refactor: decompose provider settings snapshots * fix: restore empty provider settings factory --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: namespace provider-specific config fields into provider folders (#2661) * test: characterize provider config JSON bytes * refactor: namespace provider config fields --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: deflake refresh coalescing cleanup (#2672) * fix: bound widget-snapshot file I/O with timeout and circuit breaker (#2673) On macOS 26, opening the app-group snapshot can wedge indefinitely behind container or TCC I/O. Run snapshot reads and writes on bounded detached threads, then stop further process-local container access after the first timeout. Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * fix: stop infinite Overview flicker from agent-session rescan feedback loop (#2652) (#2674) Hovering between provider chart submenus on the Overview tab with Agent Sessions and Cost Summary enabled could flicker forever. Every menu open (including each hover-opened hosted chart submenu) kicked off an agent-session rescan whose completion unconditionally invalidated all menus and rebuilt the tracked parent in place. Overview parents cannot smart-update, so the structural rebuild replaced the hovered row and force-closed its submenu; the reopen triggered another rescan, closing the loop. Three bounded changes break it: - AgentSessionsStore no longer publishes rescans that reproduce the current local/remote session content. - menuWillOpen triggers the session rescan (and menu-open note) only for root menus, not hover-opened submenus. - Session updates invalidate menus with deferred tracked-parent rebuild and stale-content preservation, matching the store-observation path. Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: provider-owned credential adapters (#2676) * test: characterize provider credentials * refactor: add provider credential adapters * refactor: register provider cookie settings * refactor: move credential policy to providers * fix: qualify static credential adapter --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: cut over Crof and Venice plugins (#2677) Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * feat: classified errors, deadlines, and overrides for JS plugins; cut over four more providers (#2678) * feat: classify plugin fetch failures * feat: add plugin request deadlines * feat: support plugin request overrides * refactor: cut over OpenRouter plugin * feat: map plugin data confidence * refactor: cut over ClawRouter plugin * refactor: cut over Deepgram plugin * refactor: cut over sub2api plugin * style: satisfy plugin cutover lint --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * test: accept GMT/UTC alias in sub2api timezone golden (#2680) * test: accept GMT/UTC alias in sub2api timezone golden Foundation reports GMT where JS Intl reports UTC on UTC-configured machines; the plugin sends the real current zone via Intl. Proven under TZ=UTC, America/Los_Angeles, Asia/Tokyo. Unbreaks main CI after the #2678 admin-merge left this red on UTC runners. * style: satisfy redundant-type lint in sub2api golden --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: derive the remaining per-provider bookkeeping from descriptors (#2682) * refactor: derive widget colors from provider branding * refactor: generate provider manifests from one registry * refactor: colocate provider cookie import priorities * refactor: move share plan labels into descriptors * refactor: move debug defaults into provider descriptors * refactor: derive debug pane provider curation * refactor: derive provider presentation capabilities * refactor: unify standard provider config bindings * style: remove stale lint suppression * fix: use grep instead of rg in manifest regeneration script CI lint runners do not have ripgrep; grep -rlE --include is portable and matches the same files. --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: unify CLI onto descriptor-owned provider capabilities (#2685) * test: characterize CLI provider policy output * test: exempt CLI JSON golden from body limit * test: scope CLI golden lint exemption * refactor: derive CLI rendering from provider descriptors * refactor: derive CLI source availability from descriptors * refactor: finish descriptor-owned CLI credentials * refactor: derive plan history policy from descriptors * refactor: document CLI provider-specific boundaries --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: derive remaining provider registries and correct authoring docs (#2686) * refactor: derive config validation capabilities * refactor: derive provider log categories * refactor: derive menu metric capabilities * build: derive provider manifest order * fix: preserve provider bootstrap order * test: derive credential gatekeeper coverage * docs: document provider registration truth --------- Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local> * refactor: move usage presentation policy into provider descriptors (#2688) * test: pin provider presentation policy * refactor: move usage window policy into descriptors * refactor: move usage menu policy into descriptors --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * refactor: derive or justify every remaining provider special case (#2691) * refactor: derive settings provider special cases * refactor: justify usage store provider state * refactor: derive controller provider policy * refactor: derive cli provider capabilities * refactor: justify scanner provider ownership * test: gate provider special case clusters * build: refresh codex parser hash * style: satisfy special case gate lint * style: accept cli cards command size --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * test: harden the provider special-case gatekeeper and derive its findings (#2693) * test: harden provider architecture gatekeeper * refactor: derive provider presentation policies * test: pin provider dispatch constructs * test: document gatekeeper catalog lint scope --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * refactor: close remaining provider-architecture gaps and declare gatekeeper scope (#2694) * refactor: close provider architecture gaps * refactor: triage loose provider references * test: harden provider architecture gatekeeper * docs: declare gatekeeper threat model --------- Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * refactor: close provider architecture gaps (#2698) Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * test: close provider gatekeeper gaps (#2699) Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * fix: harden provider gatekeeper lexer (#2700) Co-authored-by: Peter Steinberger <steipete@clawstudio.local> * fix: scope provider gatekeeper suppressions (#2702) * test: broaden provider architecture gatekeeper (#2704) * test: normalize inline block comments in gatekeeper scanning (#2705) Blank single-line block comments with index-preserving spaces so punctuation adjacency stays visible to position heuristics; document nested-suppressed-call attribution and multi-line block comments as parser-requiring out-of-scope patterns. * fix: retain flicker-probe sessions so release builds actually record (#2709) ProbeSession(...).begin() ran as an unretained temporary whose only other reference was the driving timer's [weak self], so release-mode builds deallocated the session at creation (compiler: "weak reference will always be nil") and the probe recorded nothing. Sessions are now strongly owned in MenuSwitchFlickerProbe.activeSessions until finish() unregisters them, and ProbeSession gained a Configuration seam (timings + menu opener) so a test can run a full session against a synthetic merged menu and prove it records switch activity and frame samples. * feat: nest claude-swap accounts in dashboard snapshot (#2713) * Fix Codex manual reset refresh (#2710) * Fix Antigravity agy cold-start quota readiness wait (#2665) * fix(serve): bound the whole request head, not just each read (#2684) * fix(serve): bound the whole request head, not just each read `readRequest` applied `requestReadTimeoutMilliseconds` per `recv` with no overall limit, so a client trickling one byte just inside that window never timed out and held its connection — and the cooperative-executor thread serving it — for as long as it kept sending. The Host allowlist and bearer-token check both run after the head is read, and over-cap connections are closed rather than queued, so a few such clients denied service to well-behaved ones entirely pre-auth. Track a monotonic start time and cap each wait at the remaining budget, failing the request once the overall deadline passes. A client that merely goes silent was already handled by the per-read timeout; this covers the one that keeps sending. Verified red→green on Linux: before, a legitimate client never got a slot within a 25s budget; after, it is served at 10.26s, matching the deadline. * fix(test): gate the serve deadline suite to Linux `TestsLinux` is declared unconditionally in Package.swift, so this file also compiles on macOS, where the raw socket calls (`SOCK_STREAM.rawValue`, Glibc-only imports) do not build and broke both macOS test shards. Wrap the file in `#if canImport(Glibc) || canImport(Musl)`, matching AntigravityProcessLauncherLinuxTests and the other syscall suites here. * test(serve): inject a short deadline so the suite cannot stall others The deadline test held executor threads for the full 10s production budget, which starved unrelated suites: CLIHooksWatchSleepLinuxTests asserts a stop signal is honored within 2s and instead measured 10.31s on CI. `.serialized` only orders cases within a suite, so it does not prevent that. Make the budget injectable on CLILocalHTTPServer, defaulting to the production value, and have the test pass 1500ms. Full suite drops from ~10.5s to ~2.0s. Verified the test still fails when the deadline logic is disabled (12.37s starvation), so the shorter budget did not weaken it, and ran the full suite 8x with no flake. * docs: add CodexBar Meter to Linux desktop integrations (#2696) * feat: add KRW to the preferred currency picker (#2669) KRW was the one major Asian currency missing from the picker added in #2490, so won-billed users converted USD estimates by hand. No new exchange-rate logic is needed: fetchLatestRatesIfNeeded already merges every rate in the open.er-api.com payload, which carries KRW. Listing the code in supportedCurrencies is what opens the requiresLiveRates gate, so selecting KRW triggers the same live-fetch and 24h-cache path as the existing currencies. The hardcoded fallback rate is only read before the first successful fetch. KRW is zero-decimal and currencyString pins en_US, so ICU resolves the fraction digits and won renders without a fractional separator on any host. Refs #2449 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * Align cost cache save overshoot with documented load cap (#2703) * Align cost cache save overshoot with documented load cap * Keep preceding codex cache producer key compatible * Keep current main codex cache producer key compatible * fix: accept all shipped codex producer keys back to #2632 --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * fix(commandcode): Add support for individual-goat plan ($70/mo credits) (#2706) * fix(commandcode): Add support for individual-goat plan (/mo credits) * fix(commandcode): Support new commandcode_prod_.session_token cookie name * fix(packaging): Restore widget extension packaging in package_app.sh * fix(commandcode): preserve legacy bare-token cookie fallback Keep __Secure-better-auth.session_token as the bare-token default until a renamed production cookie is proven live; accept the new commandcode_prod_ cookie names additively with regression coverage for both name families. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for review round (#2710, #2665, #2684, #2706, #2703, #2669) * feat(cli): add built-in web dashboard to codexbar serve (#2715) GET / now serves a self-contained HTML page that polls /dashboard/v1/snapshot and renders provider cards with usage windows, credits, and cost. The static UI stays unauthenticated (it carries no account data); tokens are entered in-browser, kept in localStorage, and sent as a bearer header. No visibility gating on polling so embedded panes that report document.hidden permanently still refresh. * Document Antigravity data sources when the app is closed (#2666) * Document Antigravity sources when the app is closed * docs: keep Antigravity experimental tag and reflect cold-start readiness wait (#2665) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * fix(cli): deliver late serve results instead of discarding them (#2717) When a serve operation outlived its request deadline, the coordinator marked the slot timed out and threw away the eventually-produced value: it never reached the accept commit (response cache) and the queued same-config successor restarted the source from zero. On machines where the dashboard snapshot build outlasts --request-timeout, GET /dashboard/v1/snapshot 504ed forever while each retry burned another full cost-history scan. Timed-out sources now still commit through accept and hand the fresh result to a same-fingerprint successor without rerunning the source; changed-config successors promote as before. The web UI shows a friendlier 504 message noting it retries automatically. * feat(cli): add --identity redacted|full to the dashboard snapshot command (#2716) The one-shot `codexbar dashboard` always redacted account identity even though the builder supports full mode. Personal dashboards on trusted private networks (e.g. a tailnet portal) want real account emails. The serve HTTP transport is unchanged and stays always-redacted; --identity none is rejected. * feat(cli): add --output <path> to the dashboard snapshot command (#2719) Static-webroot publishers currently shell-wrap the one-shot command (codexbar dashboard > tmp && install tmp target) to avoid readers seeing a partial document. --output writes the snapshot atomically instead: staged temp file in the destination directory, fsync, rename(2) over the target, 0644. Stdout stays untouched when the flag is absent and silent on success when writing to a file. Empty paths are an args error and a missing parent directory fails with a clear message; directories are deliberately not created. * feat(serve): render claude-swap accounts in the web dashboard; add --identity full (#2720) The built-in web dashboard ignored the dashboard-v1 accounts[] array, so multi-account Claude setups showed a single ambient row, and the serve transport's always-redacted identity left the email unknown. Per-account sections now replace the ambient windows when accounts are present (label fallback when identity is redacted), accountsError surfaces on the card, and a serve-level --identity redacted|full startup flag reuses the dashboard command's decoder so trusted private deployments can show real emails. Default stays redacted. * docs: retitle unreleased to 0.48.0 and order changelog by user impact * feat(serve): spend charts, provider brand icons, grouped card layout for the web dashboard (#2722) The web dashboard showed only aggregate cost numbers while the app has graphs, used a plain top-stripe accent, and nested claude-swap accounts as rows inside one card. It now fetches /cost daily buckets and renders an inline SVG spend chart per provider (failures never block the snapshot render), serves the app's provider brand SVGs from an embedded /icons/ route (names validated against the embedded set; immutable cache), and lays out multi-account providers as one card per account under a titled group with status pills, replacing the border stripe with an icon-led header. * fix(serve): hide the web dashboard status chip when no provider status exists (#2723) The serve snapshot collector never fetches provider status pages, so status is always nil and every card rendered a gray 'unknown' chip. Render the chip only when the snapshot actually carries status data. * chore: bump version to 0.48.0 (112) and date the changelog * style: fix 69 SwiftLint violations from serve web-dashboard merges The serve dashboard PRs (#2715-#2723) merged during the GitHub Actions outage without CI lint validation. Mechanical, behavior-preserving fixes: - CLIServeProviderIcons.swift: generator now emits scoped swiftlint:disable/enable line_length around the base64 data (icon payload is byte-identical after regeneration) - CLIServeWebUI.swift: moved the html template constant to CLIServeWebUI+HTML.swift (enum body 1093 -> under 800 lines); the string literal content is verbatim-identical - Data->String conversions switched to failable String(bytes:encoding:) with fallbacks, matching repo convention - Long @Option help strings wrapped via adjacent + concatenation * chore: publish 0.48.0 appcast entry * chore: open 0.48.1 unreleased changelog section * fix: make usage labels follow bar fill (#2744) * feat(cli): paint the serve dashboard instantly and stream data in (#2746) Three layers, one goal: never make the browser wait on a snapshot build (measured 46.5s cold vs 22ms warm on the portal host). - Stale-while-revalidate: expired cache entries within staleTTL answer immediately from the last-good response while a coalesced background rebuild commits fresh data. Applies to /usage, /cost, and the dashboard snapshot; failure-fallback rules are unchanged. - Snapshot query params (schema stays v1): ?provider=<id> builds and caches one provider row independently; ?detail=shell returns config-only rows with no provider fetches or cost scans. - Web UI: renders the last snapshot from localStorage instantly on revisit, paints skeleton cards from the shell on cold start, fills each card as its provider resolves, then hands off to normal polling. * fix: pull 0.48.0 from the appcast — packaged app crashes at launch loading the CodexBarCore resource bundle (#2738, #2730) * fix: resolve CodexBarCore resource bundle safely in packaged apps 0.48.0 crashed at launch on every machine without the build worktree: the plugin runtime's Bundle.module accessor only probes the app root and the compiled-in build path, but the packaged app ships the bundle in Contents/Resources. Route all CodexBarCore resource loads through a safe resolver (mirroring the existing localization/brand-icon pattern), and add a gate test forbidding raw Bundle.module in CodexBarCore. Fixes #2738. Fixes #2730. * chore: bump version to 0.48.1 (113) and date the changelog * feat(cli): default dashboard identity to full emails (#2748) Redaction was the dashboard-v1 default at every layer; the --identity flag already existed on both the one-shot command and serve. Flip the default to full identity and keep --identity redacted as the opt-in privacy setting for snapshots that cross untrusted networks. The web UI stops re-redacting rows before persisting them to localStorage so cached paints match what the server serves in either mode. * docs: update appcast for 0.48.1 * Decode Codex monthly credit limit from spend_control.individual_limit (#2737) * Decode Codex monthly credit limit from spend_control * Keep spend_control below root and rate_limit precedence * Centralize credit-limit precedence in resolvedIndividualLimit * docs: move Codex spend_control credit-limit entry to 0.48.2 (#2737) * fix(cli): keep claude-swap account emails when the usage fetch fails (#2752) An account's name was derived only from its usage snapshot, which the projection returns as nil for any status other than ok. Accounts with expired or missing credentials therefore lost their identity and fell back to the hardcoded "Account N" slot label, even though claude-swap reports an email for every slot and the menu bar app shows it. Resolve the email from the projection's displayLabel when the snapshot has none, and feed that single value through the existing identity-mode treatment for both label and identity so redacted mode stays honest. * fix(zai): parse CREDIT_LIMIT quota entries; restore 5-hour primary reset (#2751) * fix(zai): parse CREDIT_LIMIT quota entries from credit-based plans z.ai's GLM Coding Plans (lite/standard/pro) now return CREDIT_LIMIT instead of TOKENS_LIMIT from the quota API. Both the Swift parser and the bundled zai.js plugin rejected the unknown type, leaving accounts stuck at 100% remaining / 0% used and letting the reset display fall back to a non-5-hour limit. Treat CREDIT_LIMIT like TOKENS_LIMIT for window selection, window minutes, and the 5-hour reset description; label credit plans as "Credit quota" / "Session credit quota"; accept "level" as a plan name in the plugin for Swift parity. Adds a CREDIT_LIMIT Swift/JS parity fixture. Squashed from PR #2725. Fixes #2724 * test(zai): lock CREDIT_LIMIT parsing and 5-hour reset selection (#2724, #2712) Parser-level regression tests built from the exact payload reported in #2724: the 5-hour credit window becomes the primary lane (300-minute window, 3.55% used, "5-hour" reset description) and the weekly credit window the secondary, with the primary reset timestamp taken from the 5-hour entry rather than a fallback limit's longer schedule (#2712's reported symptom). Adds the changelog entry crediting @stuible. --------- Co-authored-by: Josh Stuible <joshstuible@gmail.com> * fix(claude): render the menu-bar indicator from the active claude-swap account (#2750) * fix(claude): render the menu-bar indicator from the active claude-swap account When the claude-swap adapter owns Claude account presentation (2+ accounts, or 1 with the single-account opt-in), the menu renders adapter account cards while every menu-bar icon path still read the ambient Claude snapshot. With an ambient probe that yields no usable rate windows, the bar drew empty even though the adapter had fresh usage for the active account. Route the icon render, layout/reset scheduling, unified-icon provider pick, highest-usage ranking, icon observation signature, and switcher mini-bars through a single menu-bar presentation-snapshot selector that prefers the active claude-swap account snapshot and falls back to the ambient snapshot when the adapter is disabled, below its presentation threshold, or the active account has no usable usage. Fixes #2731 * test: reconcile provider gatekeeper anchors for cswap menu-bar fix * build: add packaged-app launch smoke check to the packaging pipeline (#2755) 0.48.0 crashed at launch on every machine without the build checkout because SwiftPM's Bundle.module accessor only probes the app root and the compile-time build path; local testing passed only because the build machine still had the checkout at that path. Release packaging now copies the packaged app to a temp dir and runs it with sandbox-exec denying every file read under the checkout: a deterministic CODEXBAR_RESOURCE_SMOKE=1 probe forces the resource loads that trapped in 0.48.0 (they are lazy, so a liveness check alone cannot reach them), then a 6s direct-launch liveness check guards other startup fatals. Direct binary spawn skips LaunchServices and cleanup kills only the spawned PID, so the check cannot fight a running production CodexBar. Headless runs hard-fail only on the resource-bundle trap signature. Pipeline guard suggested by the reporter of #2738. * feat: portable QuickJS plugin engine; run JS providers on Linux (#2753) * build: vendor pinned quickjs-ng engine * feat: add portable QuickJS plugin engine * refactor: remove Linux provider twins * fix: use canImport C-library chain in CLIPluginsCommand for musl * Fix CLI resource bundle resolution and packaging (#2757) * fix: ship and resolve the CodexBarCore resource bundle for CLI contexts Fixes #2756 * fix: support Linux CLI resource layouts * Add codexbar-cosmic-applet to Linux desktop integrations (#2734) * Handle Codex reset credits omitted after redemption (#2728) * Handle consumed Codex reset credits omitted by provider * Exclude expired Codex reset credits from consumption detection * docs: changelog for omitted reset credits fix (#2728) * Kimi: hide Code 7-day row when it duplicates the weekly lane (#2741) * Kimi: hide Code 7-day row when it duplicates the weekly lane GetUsages (FEATURE_CODING detail) and GetSubscriptionStats (ratelimitCode7d) report the same 7-day Code quota through two endpoints; live data shows identical usage and reset times. Hide the extra row when both lanes agree (within 1pt and 5 minutes), keep it when they diverge or the weekly detail is unreliable. * Require positive evidence before hiding Kimi Code 7-day row * Align Kimi lanes with official usage naming * fix: align menu bar reset countdown with menu (#2735) * docs: changelog for Kimi lane and countdown fixes (#2741, #2735) * feat: cut over Synthetic, Poe, xAI, and z.ai to JavaScript on all platforms (#2758) * feat(synthetic): cut over provider to JavaScript * feat(poe): cut over provider to JavaScript * feat(xai): cut over provider to JavaScript * feat(zai): cut over provider to JavaScript * style(zai): use predicate count * test: give the hung-script watchdog assertion CI headroom The 0.15s interrupt must fire promptly, not wait out the hang; the 1s elapsed bound flaked at 1.66s on a loaded ARM64 runner. 5s still proves prompt termination against an unbounded loop. * test: give hooks-watch interrupt assertion CI headroom The 0.3s stop signal must beat the 10s interval; the 2s elapsed bound flaked at 2.02s on a loaded x64 runner. 5s still proves prompt interruption. * docs: permit opt-in Claude statusLine JSON as an explicit data source (#2733) * Menu: let metric meta and reset rows wrap instead of truncating (#2742) * Menu: let metric meta and reset rows wrap instead of truncating Compact usage detail rows (#2620) put all pace detail on one line and moved reset times into the title row; longer locales (e.g. Chinese) truncate tail content. Allow the meta line and reset label to wrap to two lines so all information stays visible while keeping the compact layout. * Menu: track wrapping metric text in height-cache fingerprint * fix: track reset title width in height cache --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for wrapping metric rows (#2742) * feat: add SQLite CostUsageStore foundation (refs #2760) (#2761) * feat: cut Codex cost persistence over to SQLite, delete the JSON artifact (refs #2760) (#2762) * feat: cut Codex cost persistence over to SQLite * docs: document SQLite cost history storage * test: reconcile provider gatekeeper for the SQLite cutover * fix: share the cost store serial executor * fix: keep the cost-usage database on transient SQLite failures, only rebuild on corruption (refs #2760) (#2765) withDatabase treated every error as corruption and deleted the entire database. That destroyed user history on lock contention (app and CLI both open writable connections), disk-full, and even a plain NOT NULL constraint violation from appending rows for an unregistered path -- which then failed again on the fresh database and nuked it a second time. Classify SQLite primary result codes: transient/environment/data-shape errors (BUSY, LOCKED, FULL, NOMEM, IOERR, CONSTRAINT, READONLY, ...) now return the fallback and keep the file; corruption and schema drift (CORRUPT, NOTADB, ERROR, invalid data, incompatible schema) still rebuild. Rebuilds and preserved failures are now logged via the token-cost category so field rebuilds are observable, and a failed transaction that could not roll back drops the connection instead of leaving it wedged. Adds a failure-injection suite: constraint orphans, connection reuse after a rolled-back transaction, mid-file structural corruption, deleted -wal / stale -shm sidecars, zero-byte databases, the database path occupied by a directory, and a future schema version on disk. * fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760) (#2767) * fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760) Semantics-parity audit of the CostUsageCache -> CostUsageStore migration found four behaviors the cutover dropped: - Retention pruned only the typed discovery columns while the scanner round-trips discovery through the opaque payload, so deleted files resurfaced on the next load; prune now updates both in lockstep and resets cursors/isComplete like the old cache did. - The row budget deleted the oldest files regardless of window; the old entry budget never sacrificed in-window or recently active files (the byte budget remains the only authority that may). - Fork-parent protection ignored the lineage-only dependency key and a stale parent survived when its only referencing child was pruned in the same pass; candidates now honor the dependency key and iterate to a fixpoint. - Out-of-window files with an in-window mtime (active sessions with unscanned rows) were deletable; they are protected again. saveCodexCache budgets are injectable for tests, restoring pins for previous-report preservation across trims and the non-Gregorian catch-up report (#2703), plus strip/compaction coverage. * docs: changelog for the SQLite retention semantics parity fixes (refs #2760) * perf: precompute per-path baseline counts in saveCodexCache instead of rescanning all rows per file (refs #2760) (#2770) * test: make cost-usage debounce and token-hydration tests order-independent (#2777) - Prevent a background models.dev pricing refresh from flipping codexPricingKey mid-test. - Replace the fixed 1s poll that raced the process-global CostUsageScanExecutor. * test: add StoreStress harness and contention regression tests (#2766) * fix: stabilize OpenRouter plugin parity (#2779) * Fix Codex cost pricing race (#2776) * fix: price persisted usage at report read time * fix: reprice project usage snapshots at read time * test: add corpus-scale proof gates for the SQLite cost store (refs #2760) (#2763) * test: add corpus-scale proof gates for the SQLite cost store (refs #2760) * test: freeze scale-proof reference date and assert exact retention count * test: bound persisted size of legacy payload * fix: wrap the cost-store save cycle in one transaction so a crash mid-save is all-or-nothing (refs #2760) (#2771) * fix: wrap the cost-store save cycle in one transaction so a crash mid-save is all-or-nothing (refs #2760) * fix: inline save-transaction control so the operation closure stays actor-isolated * fix: replace the save-transaction closure with begin/end calls to satisfy older Swift 6 region analysis * test: widen slow-refresh sleeps in CLIServeRouterTests so the request deadline always wins the race on loaded CI runners * Add CodexBar Plasma integration (#2768) Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> * Add Fireworks provider (30-day billing spend) (#2687) * Add Fireworks provider showing 30-day billing spend * Fix Fireworks URL/auth interpolation and test assertions * Validate Fireworks account slug; add malformed-slug test * fix: keep recently modified files out of cost-store window pruning (refs #2760) (#2764) * fix: keep recently modified files out of cost-store window pruning (refs #2760) * fix: use inclusive calendar-day bounds for retention mtime recency --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> * docs: changelog for Fireworks provider and calendar-day pruning fix (#2687,…
Summary
redeeming/redeemedrows and all existing account, timestamp, boundary, and two-sample safeguardsWhy a follow-up is needed
#2710 was merged with support for a stable credit row transitioning from
availabletoredeemed. A redacted provider trace recovered after that merge showed a different post-redemption representation: the successful inventory omitted the consumed row entirely and returnedavailableCount: 0.The merged implementation therefore remains conservative for the observed real response and may continue preserving the stale pre-reset snapshot.
Real provider behavior (redacted)
Observed on CodexBar 0.47.0 with a signed-in Codex OAuth account. Account identity, tokens, and reset-credit IDs are omitted.
The successful reset-credit inventory persisted with the post-redemption observation was:
{"availableCount":0,"credits":[]}A later read-only live OAuth fetch returned the same successful empty inventory. This shows that the consumed credit is omitted in the observed provider response instead of being retained as a
redeemedrow.After-fix real-provider smoke (redacted)
On 2026-08-07, I built and launched the exact PR head
c07f9d43as CodexBar 0.48.0 (build 112) with./Scripts/compile_and_run.sh. The release bundle remained running, and its packagedCodexBarCLIthen completed a read-only live OAuth provider fetch without error:{ "source": "oauth", "updatedAt": "2026-08-07T08:39:40Z", "weekly": { "usedPercent": 41, "windowMinutes": 10080, "resetsAt": "2026-08-13T09:29:11Z" }, "resetCredits": { "availableCount": 0, "credits": [] } }Account identity, tokens, and credit IDs are omitted. This confirms that an after-fix bundle starts successfully and that its real Codex OAuth provider path still accepts the currently observed successful empty reset-credit inventory.
This smoke run does not claim a second live redemption: by the time the fixed bundle was available, weekly usage had advanced to 41% and the account had no reset credit remaining, so an
available-> omitted transition could not be triggered again. The exact 99% -> 0% omission decision path is therefore still validated by the focused production-code replay below.Safeguards
The omission path is accepted only when:
available;redeeming/redeemed, or omit it while reporting a lower aggregate available count;A single missing inventory does not bypass the early-reset guard.
Validation
CodexWeeklyResetConfirmationTests: 19/19 passed./Scripts/compile_and_run.shbundle atc07f9d43: CodexBar 0.48.0 (112) launched and remained runningCodexBarCLIlive OAuth fetch: succeeded with weekly usage 41% and reset-credit inventoryavailableCount: 0,credits: []DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer make check: SwiftFormat 0/1806, SwiftLint 0 violations/1805, all repository gates passedDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer make test: 824 selections across 69 groups, 69 first-pass successes, 0 failures, 0 retries, 0 timeoutsgit diff --check origin/main..HEADThe account has no reset credit remaining, so the after-fix validation replays the exact redacted 99% -> 0% timing/boundary shape through the production decision code; it is not presented as a second live redemption.
No UI changes; screenshots are not applicable.