Wait for OpenCode Go Zen balance in CLI usage reads - #2583
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7f5e59748
ℹ️ 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".
| /// than the subscription page is worth waiting for (bounded by the balance request timeout). | ||
| /// The menu-bar app keeps the short optional join grace so refreshes never stall behind it. | ||
| static func shouldWaitForZenBalance(context: ProviderFetchContext) -> Bool { | ||
| context.runtime == .cli |
There was a problem hiding this comment.
Exclude guard fetches from the Zen-balance wait
Using runtime == .cli also enables this policy for codexbar guard, whose CLIGuardCommand.guardFetchOutcome only consumes the subscription rate windows and wraps the whole operation in the user-specified guard deadline. If the subscription page succeeds but the optional Zen request is slow—especially when its workspace and billing requests run sequentially—a short --timeout can now produce an unavailable/timeout decision (or fail open) instead of evaluating the already-fetched quota; previously the 250 ms join returned that quota promptly. Restrict the wait to usage reads, or disable optional usage in the guard context.
Useful? React with 👍 / 👎.
| workspaceID: workspaceID, | ||
| cookieHeader: requestCookieHeader, | ||
| timeout: timeout, | ||
| timeout: waitForZenBalance ? min(timeout, self.optionalZenBalanceTimeout) : timeout, |
There was a problem hiding this comment.
Preserve the configured timeout for required Zen fallback
For a Zen-only account whose /go page has no subscription fields, requiredZenBalanceFallback reuses this task as the required source of the snapshot. With CLI waiting enabled, this line shortens that task from the caller's configured timeout (commonly 60 seconds) to 5 seconds, so a balance response arriving after 5 seconds now fails the entire CLI read even though it is within --web-timeout; the cap should apply only while the balance remains optional, or the fallback should retry with the original timeout.
Useful? React with 👍 / 👎.
|
Codex review: needs changes before merge. Reviewed August 4, 2026, 7:10 AM ET / 11:10 UTC. ClawSweeper reviewWhat this changesThe PR makes OpenCode Go CLI usage snapshots wait for Zen balance data within a five-second optional-request budget while preserving the short wait for app, guard, and diagnostic reads. Merge readinessKeep open: the PR has strong real-account proof, but the latest timeout calculation can still discard a Zen balance that completed before the five-second deadline when subscription parsing finishes later. Priority: P2 Review scores
Verification
How this fits togetherCodexBar fetches OpenCode Go subscription usage and a Zen prepaid balance concurrently, then returns a provider usage snapshot to the CLI and menu app. The changed policy determines how long each caller waits for the optional balance before producing that snapshot. flowchart LR
A[OpenCode Go pages] --> B[Concurrent subscription and Zen requests]
B --> C[Read policy]
C --> D[CLI completeness budget]
C --> E[App and guard short grace]
D --> F[Usage snapshot]
E --> F
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Retain the task result or arm its optional deadline when the Zen task is created, then add a regression where Zen completes before five seconds but subscription parsing completes afterward. Do we have a high-confidence way to reproduce the issue? Yes—source establishes a controlled path: let the balance finish before five seconds, delay subscription parsing beyond five seconds, and the zero-duration join can race away the completed result. The PR body also provides real-account evidence for the underlying current-main omission. Is this the best way to solve the issue? No—the elapsed-duration subtraction bounds the wait but does not retain a result that completed before the deadline. A race or completion record established at task creation is the narrower safe solution. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a82f509ea8e7. LabelsLabel justifications:
EvidenceAcceptance criteria:
What 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 (10 earlier review cycles; latest 8 shown)
|
|
Completed the maintainer follow-up in
Proof:
No merge performed. |
5564c2c to
d97edf5
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d97edf5fb1
ℹ️ 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".
| - About: link the Website entry to codex.bar. | ||
|
|
||
| ### Fixed | ||
| <<<<<<< HEAD |
There was a problem hiding this comment.
Resolve the changelog conflict markers
The commit leaves an unresolved merge block in the 0.47.0 release notes; any rendered or published changelog will expose <<<<<<</=======/>>>>>>>, and the OpenCode Go fix is duplicated under the wrong historical release. Resolve the block by retaining the Alibaba entry and removing the duplicate OpenCode Go entry and markers (git diff --check also reports all three markers).
Useful? React with 👍 / 👎.
d97edf5 to
8f4efaf
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8f4efafc20
ℹ️ 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".
| timeout: waitForZenBalance | ||
| ? .seconds(self.optionalZenBalanceTimeout) | ||
| : Self.optionalZenBalanceJoinGrace) |
There was a problem hiding this comment.
Start the optional Zen timeout when its task starts
When the subscription page is slow and the Zen request hangs, this five-second timer is not armed until after the subscription task has completed and been parsed, even though the Zen task started concurrently and still receives the caller's potentially 60-second timeout at line 157. In the current revision, a four-second subscription fetch can therefore be followed by another five-second join, making the CLI wait roughly nine seconds for a supposedly five-second-bounded optional request. Start the optional deadline with the Zen task, while restarting it with the configured timeout if it becomes the required Zen-only fallback, or subtract the task's elapsed time here.
Useful? React with 👍 / 👎.
|
@clawsweeper re-review Fixed the changelog conflict block from the last review: removed the conflict markers, retained the Alibaba 0.47.0 entry, removed the duplicate historical OpenCode Go line, and verified |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review Fixed the P1 from the last review: the optional Zen-balance deadline now starts when the balance task is created, so a slow subscription cannot stack a second full five-second wait on top of it. Added a regression covering a slow subscription plus a hung balance ( |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff4df124ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let remaining = .seconds(Self.optionalZenBalanceTimeout) - (ContinuousClock.now - startedAt) | ||
| return max(Duration.zero, remaining) |
There was a problem hiding this comment.
Preserve balances completed before the deadline
When the subscription request or parsing takes more than five seconds but the Zen task has already completed successfully, this returns a zero timeout and then races an observer of the completed source task against Task.sleep(for: .zero). The timeout task can win and cancel/discard that already-available balance, so the new completeness path can still omit providerCost nondeterministically on slow subscription responses. Start and retain the timeout race when the Zen task is created, so completion before the deadline is recorded independently of when subscription parsing finishes.
Useful? React with 👍 / 👎.
|
Landed. Verification before merge (independent maintainer-agent review, distrust-first):
Known theoretical nit (not a blocker, strictly better than main): if the subscription page itself takes >5s, an already-arrived balance can still race a zero-clamped join timeout and drop nondeterministically. Main dropped it unconditionally in that scenario. |
* Port upstream 0.48.0: bound serve request heads * Test serve deadline-driven connection recovery * Port upstream 0.48.0: serve dashboard + snapshot contract * Port upstream 0.48.0: codexbar dashboard command * fix(dashboard): coordinator lost-wakeup/cancellation, daily totalCost wire, bare --output, camelCase schema F1: coordinator.rs registers the Building waiter's Notified while the lock is held (await_build helper), closing the notify_waiters lost-wakeup window; a BuildGuard resets a stranded Slot::Building to Empty + wakes waiters when a build is cancelled or panics. Adds deterministic lost-wakeup + cancel/panic regression tests with tokio::time::timeout bounds. Single-flight, TTL, late-result delivery, and errors-not-cached semantics preserved. F2: data.rs emits the upstream daily wire key 'totalCost' (was cost_usd); dashboard.html gates on 'daily.some(v => (v.totalCost || 0) > 0)' and every per-row read uses 'd.totalCost'. Adds wire/HTML agreement and positive/zero/ empty chart behavior tests. F3: dashboard.rs write_atomic treats an empty Path::parent as '.' so '--output bare-name.json' writes to the working directory. Test covers bare relative create, replacement, and no temporary leftover. F4: AccountPayload and StatusPayload serialize camelCase (updatedAt) to match pinned v1; golden assertions verify 'updatedAt' exists and 'updated_at' does not. * Port upstream 0.48.0: OpenCode Go per-model daily cost breakdown 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 Codex/Claude. Rows with no modelID fall back to an 'unknown' bucket; whitespace-only ids collapse to 'unknown', whitespace-padded ids merge with the trimmed bucket (upstream steipete#2649). Provider-local: the SQL extraction, model normalization and (day, model) aggregation live in the OpenCode Go local reader. The shared cost surfaces are reused, not duplicated -- get_daily_cost_history gains an 'opencodego' arm and CostScanner gains scan_opencodego_with_cancel that maps the provider-local summary onto the existing CostSummary (total_cost_usd, by_model, sessions_count, period), so the chart's local-usage panel and daily cost history treat OpenCode Go like Codex/Claude without a bespoke chart surface. No Codex/Claude pricing policy is copied; no duplicate chart machinery. Tests: 11 focused regressions covering multiple models/days, same-model merge, step-finish model inheritance, unknown/whitespace-only/whitespace-padded model ids, zero-cost rows, malformed rows, history-window exclusion, local-day boundary keying, deterministic (day, model) ordering, summary aggregation, and Zen-wait independence of the pure aggregation. * Port upstream 0.48.0: parse CommandCode rolling windows + GOAT plan F12 (steipete#2630): parse windowLimits.fiveHour/weekly (root or nested in credits) into primary/secondary rate windows with number-or-string coercion and epoch-s/epoch-ms/ISO-8601 resetAt handling; monthly grant moves to tertiary and uses the plan catalog for its total. F13 (steipete#2706): add the individual-goat plan (0/mo) to the plan catalog, recognize the new commandcode_prod_.session_token cookie names ahead of the legacy better-auth family (bare tokens keep the legacy name), and narrow pasted headers to the session cookie per upstream CommandCodeCookieHeader.override. Regressions: both upstream window-limits fixtures copied verbatim; cookie priority/case-folding/family tests; plan catalog + login-method and monthly-window mapping tests. * Port upstream 0.48.0: derive OpenRouter key meter from server remaining F14 (steipete#2612): the key-limit meter and its left/used text now come from the server-reported current-period limit_remaining (clamped to [0, limit]: negative reads exhausted, above-limit reads 0%) instead of lifetime usage. Without a server remaining, fall back to the period usage matching the declared limit_reset window, then cumulative usage; keys without any usable quota source keep the meter hidden. Adds wire decoding for limit_remaining/limit_reset. * Port upstream 0.48.0: preserve ZoomMate browser cookie scope F16 (steipete#2627): browser-imported cookies keep their raw host scope instead of being merged into one header reused on host failover. Chromium/Firefox host keys carry the scope (leading '.' = parent-domain), so each destination host (ai.zoom.us / zoommate.zoom.us) now gets its own header via RFC 6265 domain matching: parent-domain sessions reach both API hosts, host-only cookies never leak onto sibling hosts, non-API hosts are never destinations. Upstream fixture issue-2507-cookie-scope.json copied verbatim; regression tests cover the per-host partition, suffix-attacker rejection, empty domains, and the hostOnly/domain matrix. * Port upstream 0.48.0: decode Copilot AI credits counter A15 (steipete#2593/steipete#2613): quota snapshots now decode credits_used (number or string) for token-billed seats. The absolute counter stays off the rate-window path — it surfaces as an informational extra window (ai-credits), matching the existing snapshot/bridge/diagnostics pipeline without inventing a fake quota denominator. Upstream carriesCreditsCounter parity: zero-entitlement placeholder snapshots still yield their counter, so a business seat with no renderable quota window no longer blanks out — its snapshot becomes an informational credits row (preferred premium-, then chat-classified entries) instead of the previous hard error. Seats without any counter keep the existing token-billing error. * Port upstream 0.48.0: classify Claude OAuth refresh failures, terminal backoff F3 (steipete#2650): on Windows the credential file is readable, so the upstream touch-completes-but-unreadable state has no equivalent — the matching provably-unrecoverable-by-retry state is the refresh endpoint rejecting the stored refresh token (400/401/403). Those now classify as terminal: a 5-minute per-source backoff (upstream defaultCooldownInterval) instead of a doomed grant on every poll, and an honest re-login message without the useless retry tail. Transient failures (network, 429, 5xx) keep a short 20-second cooldown (upstream shortCooldownInterval) so recovery still lands quickly. Successful refresh clears state; a re-login via the shared credentials file is adopted past any backoff as before. Regressions: classification matrix, long/short backoff gating + purge, distinct terminal/cooldown user messages. * Port upstream 0.48.0: bounded Zen balance wait in OpenCode Go usage reads F15 (steipete#2583): the Zen balance now joins usage reads with an explicit policy bound measured from task creation — CLI usage/serve /usage reads (requires_optional_usage_completeness, new FetchContext field) join for the remainder of the 5 s optional-balance budget; background/UI/guard/ diagnose hooks keep the 250 ms grace, so a slow subscription fetch can never stack a second full wait. Local (SQLite) reads gain the same optional enrichment as web reads, matching the upstream local strategy. The balance fetch itself is the upstream chain: dashboard page parse first, dedicated billing server-fn (raw 1e-8 USD behind a customerID marker, RSC-fragment tolerant) as fallback, 25 ms start delay, bounded per-request, and abandoned-over-budget tasks are aborted instead of leaking. A zero-cost balance embedded in the usage page still wins without any extra request. A14 (per-model cost breakdown by day) intentionally NOT in this commit. * Port upstream 0.48.0: Claude OAuth refresh gate + serve/opencodego fixes (M1-M4) M1: from_http_status terminal iff (400|401) AND OAuth error == invalid_grant (case-insensitive). 403 and 400/401-without-invalid_grant are transient. M2: terminal gate indefinite until credential fingerprint changes or success clears; transient base 5min flat; eliminates repeated dead-grant retries. M3: serve /usage sets requires_optional_usage_completeness false (background poll grace); CLI usage remains true. M4: abort spawned Zen balance task on usage-page/parse error before early return. * Port upstream 0.48.0: Kimi/GLM/z.ai China routing (WS5) - Kimi Desktop monthly membership pool enrichment: read-only WAL-safe kimi-auth token reader for the Electron Chromium store (%APPDATA%/kimi- desktop), AES-256-GCM via existing browser cookie crypto; Code API + CLI snapshots merged with Monthly + Code 7-day membership windows (steipete#2622/A10). - Cookie Source Off disables Kimi Desktop + browser import (manual cookie headers keep working) (steipete#2623/A12). - Moonshot/Kimi Open Platform: MOONSHOT_REGION + region-bound CODEXBAR_MOONSHOT_API_KEY(_REGION) binding so CN/intl keys stay on their issuing hosts; provider renamed per upstream (steipete#2621/A11). - GLM Coding Plan: 5-hour TOKENS_LIMIT window is primary, weekly secondary, MCP rendered as a separate named window; plan name falls back to plan/plan_type/packageName/level (steipete#2621/A11). - z.ai region routing: BIGMODEL/ZHIPU(ZHIPUAI)/GLM env aliases and coding-relay key files only for BigModel CN; canonical cross-region endpoint overrides rejected before bearer auth (steipete#2623/A12). - Shared WAL-safe read-only SQLite helper (core::sqlite) replacing the OpenCode Go-local copy, reused by the Kimi Desktop reader (steipete#2544 pattern). * Port upstream 0.48.0: unify Pi-family (pi + OMP) agent sessions (WS6) - One dialect-aware scanner: live pi/OMP process detection (basename + bun-shim handling, helper filtering), session-jsonl correlation per CWD, PID-only rows when no transcript can be attributed, upstream OMP profile / PI_CONFIG_DIR / --session-dir / settings.json root resolution with the same fail-closed validation, upstream fixtures copied verbatim (steipete#2626/A13). - Wire shape: AgentSession gains optional dialect + sessionName; --json stays legacy Codex/Claude-only (v1) while --json-v2 emits the complete array; SSH session discovery negotiates --json-v2 with --json fallback. - Local scanner adopts the shared bounded directory budget; provider labels Pi/OMP in the sessions UI (bridge DTO + locale keys). - Remote plumbing (RemoteSessionFetcher) moved to agent_sessions/remote.rs and the Pi scanner split into pi_family/{mod,parser,roots} to keep every file under 1000 lines. * fix(dashboard): enable waiter notification under the decision guard (F1) Corrects the lost-wakeup fix in 87ff071: await_build re-acquired the slot mutex AFTER the decision guard was released, so a build completing in that gap could fire notify_waiters (and swap the slot) before the waiter ever registered — the waiter then slept forever on an already-fired Notify. Now the waiter constructs and enable()s an OwnedNotified (which owns the Arc<Notify>, Send+Sync) while still holding the SAME decision mutex that observed Slot::Building, carries the registered future out of the critical section, and awaits it only after the guard drops. The builder can update the slot and notify_waiters only while holding that same mutex, so registration is provably ordered before any wakeup attempt for this build. await_build helper removed. Regression: completion_in_decision_window_sets_waiter_notified drives the waiter with manual polls and forces build completion + notify_waiters into the exact decision->await window (slot manipulated directly; zero scheduler dependence). Existing racing/cancel/panic regressions and single-flight, TTL, late-result, and errors-not-cached behavior unchanged. Also repairs two FetchContext initializers in source.rs for the integrated requires_optional_usage_completeness field (E0063 at d2a63eb; false = FetchContext::default, no behavior change). Verified: cargo fmt --all --check clean; cargo clippy -p codexbar --all-targets -- -D warnings clean; 10/10 coordinator tests pass (2 threads). * Port upstream 0.48.0: Codex cost-scanner robustness (F1,F2,F18,F19) WS2 — Codex cost cache and scan robustness, ported from upstream 0.48.0. F1 (cache bounds): add CostUsageCacheBudget module with upstream's 256 MiB save / 320 MiB load / 25 000 entry caps. Load refuses to decode artifacts above MAX_LOAD_BYTES (cheaper to rebuild bounded). Save prunes out-of-window entries, then trims oldest in-window entries to fit the budget, protecting partially-parsed (growing) files so append-only resume keeps its catch-up progress. F2 (fork catch-up resume): validate the cached resume offset is a real line boundary (byte at offset-1 == newline) before resuming an append-only parse. A partial trailing-line write leaves the offset mid-line; resuming there corrupts the first record. When the check fails, fall back to a full re-parse from zero instead of the append-only merge. F18 (priced + unpriced Auto Review): codex-auto-review and the model-less sentinel are now deliberately unpriced routing rows — tokens counted, by_model row present with 0 cost, no fallback to gpt-4o rates. Add typed ModelPricingCompleteness (Complete | Partial{unpriced_models}) to CostSummary so the dashboard can label a partial breakdown. F19 (overshoot contract + predecessor keys): document the save/load overshoot contract — save may exceed MAX_FILE_BYTES up to MAX_LOAD_BYTES when protected entries cannot be trimmed further. Predecessor-key acceptance is N/A locally (local cache uses filename -v1 versioning, no producer-key field); documented as a documented divergence. * Port upstream 0.48.0: Codex windows/pricing (F5,F6,C4) WS3 — Codex duration classification and pricing, ported from upstream 0.48.0. F5 (duration classification 5h/weekly/30-day): centralize duration policy in RateWindowCadence (Session/Weekly/Monthly/Unknown) with from_minutes() and from_seconds(). Add MONTHLY_WINDOW_MINUTES (43 200) next to the existing SESSION/WEEKLY constants. Update codex_window_role to use RateWindowCadence so 30-day windows classify as Monthly instead of being swallowed into Weekly. C4 (Fast cost semantics + Terra/Luna refresh): add codex_api_fast_multiplier() (gpt-5.4/5.4-mini/5.6-sol/5.6-terra/5.6-luna → 2.0; gpt-5.5 → 2.5; else nil) and codex_fast_cost_usd() (standard cost × multiplier with long-context guard at 272 000 input). Wire into codex_costs::codex_cost_usd after canonical resolution fails but before legacy gpt-4o fallback, for fast/priority model IDs. Refresh Terra rates (2e-6/1.2e-5, long 4e-6/1.8e-5) and Luna rates (2e-7/1.2e-6, long 4e-7/1.8e-6). Fast detection is name-based locally (upstream uses a priority-trace SQLite DB scan — documented divergence). * Port upstream 0.48.0: complete WS2+WS3 follow-up (A16,F6,F8,F5) A16 (scan completeness JSON): add historyCoverageIsEstablished to CostSummary and surface it in the CLI cost JSON as historyCoverageIsEstablished (Bool?, null for non-Codex providers). Set from cache freshness + catch-up state so callers know when a re-scan is pending. Provider-native-only flag is N/A locally (no pi/OMP mirror sessions) — documented divergence. F6 (manual reset backfill): add codex_reset_backfill in Tauri providers.rs — backfills missing resets_at/reset_description on fresh Codex windows from the cached snapshot when the cached reset is still future (fresh used_percent untouched). Wired into refresh_provider before publishing so every surface (tray, CLI, frontend) sees the backfilled reset. Implemented through the existing provider refresh abstraction, not a generic trait hook. F8 (cached spend during refresh): add refreshing + stale_updated_at to UsageSpendRow (backward-compatible optional fields). When the Codex cache was pruned for budget (previous_report set), the spend row shows the stale timestamp and refreshing indicator so the UI can show old data while a re-scan rebuilds the artifact. Frontend UsageSpendTab renders the indicator and uses the UsageSpendRefreshing locale key. F5 (monthly cadence wiring): add Monthly to WindowRole (managed accounts) and wire monthly through the ambient provider (normalize_array_windows 4-tuple routes monthly to UsageSnapshot.tertiary). Add tertiary_label to bridge ProviderUsageSnapshot (duration-cadence label via RateWindowCadence). Frontend MenuCard.tsx uses tertiaryLabel with monthly localization (ProviderMonthly). Tray provider_status_label for Codex picks first non-informational lane (session → weekly → monthly). CLI usage.rs appends a monthly lane line with RateWindowCadence-based label. Test added for Monthly role classification. * Port upstream 0.48.0: fix fmt/clippy integration issues - Remove untracked package-lock.json (pnpm repo, npm artifacts incompatible) - cargo fmt --all: bridge.rs, providers.rs, usage_spend.rs, tray_bridge.rs - clippy: move constant-size budget assertion into const block (assertions_on_constants) - clippy: add TestCache type alias to simplify test helper return type (type_complexity) - clippy: collapse nested if-let in tray_bridge.rs codex_lane_headline_window using let-chains (let-chains stable since 2025 edition) * Port upstream 0.48.0: C4 centralize fast suffix stripping (audit fix) Extract codex_fast_base_model() that strips -fast/-priority suffixes. Both codex_api_fast_multiplier() and codex_fast_cost_usd() now use it so the original suffix does not leak into the Standard base lookup. Previously codex_fast_cost_usd passed the original model name to codex_cost_usd, which failed for suffixed IDs like gpt-5.5-fast. Tests added: - test_codex_fast_cost_usd_suffixed_models_resolve_to_base: gpt-5.5-fast -> base gpt-5.5 × 2.5, gpt-5.6-sol-priority -> base gpt-5.6-sol × 2.0 - test_codex_fast_base_model_unsuffixed: unsuffixed and unknown models resolve to themselves. * Port upstream 0.48.0: F8 clear previous_report after full scan (audit fix) A completed full scan rebuilds the cache for the current window, so any prior catch-up state is no longer pending. Clear previous_report before save_cache so the persisted artifact no longer signals stale/refreshing. Previously previous_report was set during save-time budget pruning but never cleared, causing a permanent Refreshing indicator. Test: previous_report_clears_after_successful_full_scan — first scan clears, inject previous_report to simulate trim, full scan clears it. * Port upstream 0.48.0: A16/F18 expose coverage+completeness in CLI JSON (audit fix) A16 historyCoverageIsEstablished and F18 modelPricingCompleteness were added to CostSummary in the prior follow-up but never wired into the CLI cost JSON or text output. Now: - JSON emits historyCoverageIsEstablished (bool for Codex, null otherwise) and modelPricingCompleteness ("complete" or {partial:{unpriced_models}}). - Text output labels partial pricing and partial coverage when present. - --provider-native-only flag added, maps to CostScanOptions::include_pi_sessions = false, excluding pi/OMP session mirrors. Documented divergence: no pi/OMP mirror sessions on this Windows build so the flag is accepted but has no observable effect locally. Tests: json_output_emits_a16_and_f18_fields, json_output_a16_null_for_non_codex, provider_native_only_flag_default_false. * Port upstream 0.48.0: F19 refuse oversized cache + fix trim double-subtraction (audit fix) F19: save_cache now checks the encoded JSON length against MAX_LOAD_BYTES before persisting. If the artifact still exceeds the load budget after pruning+trimming (e.g. a single protected entry alone exceeds the limit), the save is refused — no persist/refuse/rebuild loop. Extracted as CostUsageCacheBudget::should_refuse_persistence() pure helper for testability. Trim double-subtraction fix: trim_in_window_for_budget pre-subtracted droppable[0] from the initial estimate, then subtracted it again inside the loop — a double count. Now the initial estimate is the full estimated_cache_bytes and the loop subtracts each candidate once. Tests: - should_refuse_persistence_at_and_above_limit (boundary 1024/1025) - trim_estimate_no_double_subtraction_of_first_entry - trim_drops_until_target_reached_then_stops - save_cache_persists_small_codex_artifact (no false-positive) - save_cache_refuses_non_bounded_provider_oversize (Claude gate) * Port upstream 0.48.0: F2 boundary helper + scan-level negative regression (audit fix) F2 (upstream 0.48.0 steipete#2648): add boundary helper tests for all edge cases and a scan-level negative regression proving midline/truncated rewrite forces full parse (no resume from stale offset). Tests: - is_line_boundary_offset_zero_returns_true (offset 0) - is_line_boundary_offset_at_or_past_size_returns_true (EOF) - is_line_boundary_offset_exact_newline_returns_true (valid boundary) - is_line_boundary_offset_midline_returns_false (fall through) - is_line_boundary_offset_missing_file_returns_false (probe fail) - cost_scan_midline_rewrite_forces_full_parse_not_resume (scan-level) * Port upstream 0.48.0: F5 cadence/routing/headline regression tests (audit fix) F5 (upstream 0.48.0): boundary table for RateWindowCadence and routing regression for normalize_array_windows + tray headline preference. Tests: - cadence_boundary_session_exactly_300 (Session) - cadence_boundary_weekly_10080 (Weekly) - cadence_boundary_below_monthly_43199_is_weekly (boundary) - cadence_boundary_monthly_43200 (Monthly) - cadence_from_seconds_rounding (0/neg→Unknown, 18001s→301→Unknown) - cadence_label_keys (session/weekly/monthly/unknown) - f5_normalize_array_routes_session_weekly_monthly_to_lanes - f5_normalize_array_monthly_routes_to_tertiary_not_secondary - f5_normalize_array_empty_returns_placeholder_primary - f5_normalize_array_unknown_windows_fall_to_code_review - f5_headline_prefers_non_informational_primary (Tauri) - f5_headline_falls_back_to_secondary_when_primary_informational (Tauri) - f5_headline_falls_back_to_tertiary_when_primary_and_secondary_informational (Tauri) - f5_headline_returns_primary_when_all_informational (Tauri) * Port upstream 0.48.0: F6 reset-backfill regression tests (audit fix) F6 (upstream 0.48.0 UsageStore+CodexResetBackfill): regression tests for codex_reset_backfill covering future/stale/no-cached/non-codex/existing paths. Tests: - f6_backfills_future_cached_reset (future reset backfilled, used untouched) - f6_does_not_backfill_stale_cached_reset (past reset skipped) - f6_does_not_overwrite_existing_resets_at (fresh reset preserved) - f6_skips_non_codex_provider (Claude skip) - f6_skips_when_no_cached_snapshot (None cached) Divergence: upstream weekly-confirmation exemption N/A locally — local backfill is the observable analog (no weekly-confirmation guard exists). * Port upstream 0.48.0: fix clippy lint in audit-followup test code (audit fix) Fix 4 clippy -D warnings violations in the audit-followup test code: - collapsible_if in cli/cost.rs partial pricing label - field_assignment_outside_initializer in cli/cost.rs test - field_assignment_outside_initializer in jsonl_scanner.rs test - needless_borrows_for_generic_args in cost_scanner.rs test * Port upstream 0.48.0: F19 refusal removes preexisting destination artifact (audit fix) The prior F19 commit refused on oversized post-encode but left any existing destination cache file in place; a stale/oversized artifact could persist and trip the load-refusal path on the next scan, forcing an unnecessary full rebuild from a poisoned artifact. - save_cache now deletes the destination file on refusal (best-effort, mirroring the fs-delete idiom used elsewhere in core). - Extracted save_cache_with_limit(provider, cache, cache_root, max_load_bytes) as a private testable helper; save_cache delegates with the production MAX_LOAD_BYTES const. Production limit behavior is unchanged. Integration regressions (jsonl_scanner.rs): - save_cache_refusal_removes_preexisting_destination_artifact: precreate real destination via save_cache_with_limit(usize::MAX), trigger refusal with limit=1, assert destination gone, no tmp artifact with content, and load yields empty cache (no rebuild loop). - save_cache_at_exact_limit_is_accepted: encoded artifact at exactly the injected limit is persisted (boundary). - save_cache_one_over_limit_is_refused_and_removes_destination: one byte over limit is refused and destination removed. * fix: fan out dashboard build errors * fix: pin Windows globalization timezone module iana-time-zone resolves the Windows system zone through WinRT's Windows.Globalization Calendar class, but nothing keeps that DLL loaded. COM cleanup exercised by the notification sound/toast tests unloads it, leaving windows-core's process-static factory cache pointing into an abandoned mapping; the next get_timezone() call then access-violates (observed under LLDB; full lib suite crashed 3/3 single-threaded). Load Windows.Globalization.dll from System32 and pin it for the process lifetime before any get_timezone() call, gated behind a one-shot LazyLock so every caller waits for load+pin to settle. On pin failure return UTC without calling iana-time-zone (an AV is uncatchable). Route both project call sites (claude cli_reset, sub2api) through the new crate-internal helper. No lockfile change. * test: make globalization pin test host-independent The self-hosted PR runner is a stripped Windows image without registered WinRT types (ToastNotification not registered), where pinning Windows.Globalization.dll can fail by design; the helper then correctly falls back to UTC. Assert the environment-independent contract instead: the pin attempt settles exactly once and every caller observes the same decision.
Closes #2581
Summary
codexbar usage --provider opencodegocan silently drop the OpenCode Zen balance when the balance page responds more than 250 ms after the subscription page: the optional Zen join grace (optionalZenBalanceJoinGrace, 250 ms) resolves asniland cancels the in-flight balance request.This PR keeps the app's intentional fast refresh and changes the join contract for one-shot CLI reads:
OpenCodeGoUsageFetcher.fetchUsagegainswaitForZenBalance(defaultfalse). When enabled, the optional Zen balance is joined without the 250 ms grace, so a slower but successful balance response is included in the snapshot.OpenCodeGoUsageFetchStrategyenables the wait policy whencontext.runtime == .cli(codexbar usage,codexbar serve); the menu-bar app keeps the 250 ms bounded join.optionalZenBalanceTimeout), so a hung balance endpoint stalls a CLI read by at most ~5 s.nilbalance; the CLI read does not fail.This is the "Zen balance completeness is required for each CLI snapshot" contract from the issue review decision table. The alternative (retaining and asynchronously refreshing the last successful balance) remains open for the app/menu surface.
Tests
optional zen balance does not stall subscription usage(unchanged: app path still completes under 700 ms with a 1 s balance response)cli wait policy includes slow but successful zen balance(new: 1 s delayed valid balance is included)cli wait policy keeps subscription result when balance fetch fails(new)web strategy waits for zen balance only in cli runtime(new)Validation
swift test --filter 'OpenCodeGoUsageFetcherErrorTests|OpenCodeGoProviderStrategyTests'- 32/32 passedmake check- SwiftFormat 0 files need formatting, SwiftLint 0 violationsmake test- 758 selections / 64 groups, all passedCloses OpenCode Go provider silently drops Zen balance if balance fetch is >250ms slower than subscription fetch #2581
Summary
codexbar usage --provider opencodegocan silently drop the OpenCode Zen balance when the balance page responds more than 250 ms after the subscription page: the optional Zen join grace (optionalZenBalanceJoinGrace, 250 ms) resolves asniland cancels the in-flight balance request.This PR keeps the app's intentional fast refresh and changes the join contract for one-shot usage reads:
OpenCodeGoUsageFetcher.fetchUsagegainswaitForZenBalance(defaultfalse). When enabled, the optional Zen balance is joined with the optional-balance timeout (5 s) instead of the 250 ms grace, so a slower but successful balance response is included in the snapshot.ProviderFetchContextgainsrequiresOptionalUsageCompleteness(defaultfalse). Only usage-snapshot reads (codexbar usage,codexbar serve) set it, socodexbar guardand diagnostic commands keep the 250 ms bounded join and a slow balance cannot consume the guard deadline.--web-timeout; the required fallback is no longer capped at five seconds.nilbalance; the CLI read does not fail.Maintainer follow-up in
8fd66a4a6cbounds workspace discovery and the balance request at five seconds in the local-overlay helper and addsOpenCodeGoOptionalZenBalanceTimeoutTests.Tests
optional zen balance does not stall subscription usage(unchanged: app path still completes under 700 ms with a 1 s balance response)cli wait policy includes slow but successful zen balance(1 s delayed valid balance is included)cli wait policy keeps subscription result when balance fetch failscli wait policy keeps configured timeout when zen balance becomes required(Zen-only fallback keeps--web-timeout)web strategy waits for zen balance only on usage completeness reads(guard-style CLI contexts do not wait)Validation
swift test --filter 'OpenCodeGo...'- 34/34 passedmake check- SwiftFormat clean, SwiftLint 0 violationsmake test- 760 selections / 64 groups, all passedNote: the latest
swift-test-macos (1, 2)CI failure on this branch is an unrelated timing flake inKimiProviderTests(subscription grace is a total budget for existing usage windows, 0.25444 s vs a 250 ms budget); the PR does not touch Kimi code and the test passes locally.Real-behavior proof (redacted live
codexbar usage --provider opencodegotranscript) still needs an OpenCode Go account and is pending.Closes #2581
Summary
codexbar usage --provider opencodegocan silently drop the OpenCode Zen balance when the balance page responds more than 250 ms after the subscription page: the optional Zen join grace (optionalZenBalanceJoinGrace, 250 ms) resolves asniland cancels the in-flight balance request.This PR keeps the app's intentional fast refresh and changes the join contract for one-shot usage reads:
OpenCodeGoUsageFetcher.fetchUsagegainswaitForZenBalance(defaultfalse). When enabled, the optional Zen balance is joined with the optional-balance timeout (5 s) instead of the 250 ms grace, so a slower but successful balance response is included in the snapshot.ProviderFetchContextgainsrequiresOptionalUsageCompleteness(defaultfalse). Only usage-snapshot reads (codexbar usage,codexbar serve) set it, socodexbar guardand diagnostic commands keep the 250 ms bounded join and a slow balance cannot consume the guard deadline.--web-timeout; the required fallback is no longer capped at five seconds.nilbalance; the CLI read does not fail.Maintainer follow-up in
8fd66a4a6cbounds workspace discovery and the balance request at five seconds in the local-overlay helper and addsOpenCodeGoOptionalZenBalanceTimeoutTests.Real behavior proof
Live runs against a real OpenCode Go account, same command (
codexbar usage --provider opencodego --format json), same network, three runs each:main(e59539db, 0.47.0)providerCostmissingproviderCostmissingproviderCostmissing5564c2cf)providerCostpresentproviderCostpresentproviderCostpresentOn this network the Zen balance endpoint consistently finishes more than 250 ms after the subscription page, so
maindrops it on every run; the PR branch includes it on every run.Redacted PR-branch transcript (the output contains no cookies, account identifiers, or workspace IDs, so nothing further needed redacting):
[ { "provider" : "opencodego", "source" : "web", "usage" : { "primary" : { "resetsAt" : "2026-08-03T21:10:06Z", "usedPercent" : 2, "windowMinutes" : 300 }, "providerCost" : { "currencyCode" : "USD", "limit" : 0, "period" : "Zen balance", "updatedAt" : "2026-08-03T16:43:53Z", "used" : 0 }, "secondary" : { "resetsAt" : "2026-08-09T23:59:57Z", "usedPercent" : 9, "windowMinutes" : 10080 }, "tertiary" : { "resetsAt" : "2026-09-01T16:44:42Z", "usedPercent" : 51, "windowMinutes" : 43200 }, "updatedAt" : "2026-08-03T16:43:53Z" } } ]The same command on
mainreturns the identical shape withoutproviderCost:{ "provider" : "opencodego", "source" : "web", "usage" : { "primary" : { "usedPercent" : 2, "windowMinutes" : 300 }, "secondary" : { "usedPercent" : 9, "windowMinutes" : 10080 }, "tertiary" : { "usedPercent" : 51, "windowMinutes" : 43200 }, "updatedAt" : "2026-08-03T16:43:57Z" } }Tests
optional zen balance does not stall subscription usage(unchanged: app path still completes under 700 ms with a 1 s balance response)cli wait policy includes slow but successful zen balance(1 s delayed valid balance is included)cli wait policy keeps subscription result when balance fetch failscli wait policy keeps configured timeout when zen balance becomes required(Zen-only fallback keeps--web-timeout)web strategy waits for zen balance only on usage completeness reads(guard-style CLI contexts do not wait)Validation
swift test --filter 'OpenCodeGo...'- 34/34 passedmake check- SwiftFormat clean, SwiftLint 0 violationsmake test- 760 selections / 64 groups, all passedNote: the earlier
swift-test-macos (1, 2)CI failure on this branch was an unrelated timing flake inKimiProviderTests(subscription grace is a total budget for existing usage windows, 0.25444 s vs a 250 ms budget); the PR does not touch Kimi code and the test passes locally.