Skip to content

Use OpenRouter server remaining for key limit meter - #2612

Merged
steipete merged 6 commits into
steipete:mainfrom
Yuxin-Qiao:codex/fix-openrouter-monthly-limit-2605
Aug 5, 2026
Merged

Use OpenRouter server remaining for key limit meter#2612
steipete merged 6 commits into
steipete:mainfrom
Yuxin-Qiao:codex/fix-openrouter-monthly-limit-2605

Conversation

@Yuxin-Qiao

@Yuxin-Qiao Yuxin-Qiao commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

For OpenRouter keys whose /api/v1/key response reports limit_remaining (and optionally limit_reset), CodexBar now uses the server-provided remaining amount for the key-limit meter and the "left" dollar text instead of deriving both from cumulative lifetime usage.

Fixes #2605.

Before

With limit: 500, limit_remaining: 454.542594979, limit_reset: monthly, cumulative usage: 433.286754736, the card showed 13% left / $66.71/$500.00 left because it computed 500 - 433.286754736.

After

  • When limit_remaining is present, the meter uses it directly: 91% left / $454.54/$500.00 left.
  • When limit_remaining is absent but limit_reset identifies a window, the matching period usage (usage_monthly / usage_weekly / usage_daily) is used as the fallback.
  • Otherwise behavior is unchanged (cumulative usage fallback).

The decoded limit_remaining / limit_reset values are persisted in OpenRouterUsageSnapshot so cached snapshots keep the corrected meter.

Exhausted quota handling (review follow-up)

A finite negative limit_remaining (overspent key) is now treated as valid exhausted quota: the card shows $0.00 remaining and a full meter instead of "API key limit unavailable". OpenRouterUsageSnapshot.hasValidKeyQuota accepts any finite server remaining value and the rendered values clamp to zero / 100% used.

JavaScript provider parity

The opt-in bundled JavaScript OpenRouter provider (Sources/CodexBarCore/Resources/Plugins/openrouter.js, enabled via the plugin prototype flag) now uses the same quota source order as the Swift path: server-reported limit_remaining, then the usage field matching limit_reset, then cumulative usage, with the same zero clamp for negative remaining. A parity fixture asserts the Swift and JS snapshots agree on the monthly-limit response.

Fallback validation

OpenRouterUsageSnapshot.hasValidKeyQuota now validates the selected fallback quota value (the reset-window usage field when the server declares the window, otherwise cumulative usage) instead of requiring cumulative usage unconditionally. A response with limit_reset: "monthly" and usage_monthly therefore renders the meter even when cumulative usage is absent, matching the JavaScript provider. Covered by a Swift fetch fixture and a Swift/JS parity fixture.

Real behavior proof

Ran against a real OpenRouter key with a finite configured limit on the PR head. The live API reports limit: 500 with limit_reset: "monthly" and limit_remaining: 500, and the PR head decodes those fields and renders the key-limit meter from the server remaining value (primary.usedPercent: 0, i.e. 100% left). The fresh key has zero usage, so remaining equals the limit; the account is free-tier with a $0 balance, so a request to generate usage was not possible. A zero-usage key renders the same meter before and after this change; the distinguishing quota state (lifetime usage greater than the current reset window, as in the issue report) cannot be produced with this account and is covered by the deterministic fixtures above, including the monthly limit_remaining fixture where the old calculation yields 86.6% used and the new one yields 9.09%.

Live GET https://openrouter.ai/api/v1/key response, account fields redacted:

{"data":{"label":"[REDACTED]","is_management_key":false,"is_provisioning_key":false,"limit":500,"limit_reset":"monthly","limit_remaining":500,"include_byok_in_limit":false,"usage":0,"usage_daily":0,"usage_weekly":0,"usage_monthly":0,"byok_usage":0,"byok_usage_daily":0,"byok_usage_weekly":0,"byok_usage_monthly":0,"is_free_tier":true,"expires_at":null,"creator_user_id":"[REDACTED]","rate_limit":{"requests":-1,"interval":"10s","note":"This field is deprecated and safe to ignore."}}}

codexbar usage --provider openrouter --format json --pretty on the PR head:

[
  {
    "provider" : "openrouter",
    "source" : "api",
    "usage" : {
      "identity" : {
        "loginMethod" : "Balance: $0.00",
        "providerID" : "openrouter"
      },
      "openRouterUsage" : {
        "balance" : 0,
        "keyDataFetched" : true,
        "keyLimit" : 500,
        "keyLimitRemaining" : 500,
        "keyLimitReset" : "monthly",
        "keyUsage" : 0,
        "keyUsageDaily" : 0,
        "keyUsageMonthly" : 0,
        "keyUsageWeekly" : 0,
        "rateLimit" : {
          "interval" : "10s",
          "requests" : -1
        },
        "totalCredits" : 0,
        "totalUsage" : 0.42311394,
        "usedPercent" : 0
      },
      "primary" : {
        "usedPercent" : 0
      },
      "secondary" : null,
      "tertiary" : null,
      "updatedAt" : "2026-08-04T05:33:03Z"
    }
  }
]

Verified the CLI output contains no token or account identifier: rg -n "sk-or-v1|creator_user_id|label" returns no matches.

Test

  • swift test --filter OpenRouterUsageStatsTests - 13/13 passed, including fixtures for the monthly limit_remaining response, the missing-limit_remaining reset-window fallback, the missing-usage reset-window fallback, and the negative-limit_remaining exhausted-quota case (stubbed URLProtocol, no real credentials)
  • swift test --filter ProviderPluginParityTests - passed, including the new OpenRouter monthly-limit Swift/JS parity fixture
  • Adjacent consumer suites (OpenRouterMultiAccountTests, MenuCardModelTests, MenuCardProviderRegressionTests, PopupLocalizationTests, InlineUsageDashboardBarColorTests) - 98 tests passed
  • make check - SwiftFormat/SwiftLint 0 violations, locales/package/repo checks passed
  • git diff --check - clean

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a2f0434c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

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

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +150 to +151
if let keyLimitRemaining {
return keyLimitRemaining >= 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat negative server remaining as an exhausted quota

When /api/v1/key reports a slightly negative limit_remaining for an exhausted or overspent key, this check marks the quota unavailable. Because keyRemaining first requires hasValidKeyQuota, its new max(0, keyLimitRemaining) clamp is never reached, so the UI hides the 100%-used meter and shows “API key limit unavailable right now” instead of zero remaining. Accept finite negative remaining values and clamp them to zero, as is already done when cumulative usage exceeds the limit.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Aug 3, 2026
@clawsweeper

clawsweeper Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed August 4, 2026, 11:36 PM ET / August 5, 2026, 03:36 UTC.

ClawSweeper review

What this changes

The PR makes CodexBar’s OpenRouter key-limit meter prefer the server’s remaining quota, with reset-window and cumulative-usage fallbacks in both Swift and the optional JavaScript provider.

Merge readiness

Blocked until stronger real behavior proof is added - 3 items remain

The PR remains necessary: current main still derives the OpenRouter key meter from cumulative usage. The implementation and focused parity coverage look sound, but the supplied live run uses a zero-usage key where before and after render identically, so it needs distinguishing real-behavior proof before merge.

Priority: P2
Reviewed head: e6155a03ac8af4bb6abd0dea4b0eb9dd38c98cbd

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The patch is focused and well-covered, but its real run does not yet show the corrected behavior in a distinguishing quota state.
Proof confidence 🦐 gold shrimp (3/6) Needs stronger real behavior proof before merge: The redacted terminal output proves after-fix decoding on a real key, but its zero-usage state renders the same result before and after; deterministic fixtures do not demonstrate the observed improvement in a real setup. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: The redacted terminal output proves after-fix decoding on a real key, but its zero-usage state renders the same result before and after; deterministic fixtures do not demonstrate the observed improvement in a real setup. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 6 items Current main retains the reported behavior: Current main requires cumulative key usage and calculates both remaining dollars and percent directly from it; it has no server-remaining or reset-window selection path.
PR implements the narrow source-order fix: The PR persists the server fields, uses finite server remaining first, then reset-window usage, then cumulative usage, and clamps exhausted or above-limit values.
Swift and JavaScript paths receive fixture coverage: The PR adds four Swift quota fixtures and three Swift/JavaScript parity fixtures, including the monthly divergent response, missing cumulative usage, and above-limit server remaining.
Findings None None.
Security None None.

How this fits together

CodexBar fetches OpenRouter credit and key-quota responses, turns them into a usage snapshot, and feeds that snapshot to the menu card and inline dashboard. This change affects the percentage and remaining-dollar amount shown for configured OpenRouter key limits.

flowchart LR
A[OpenRouter credits API] --> C[Usage collection]
B[OpenRouter key API] --> C
C --> D[Quota source selection]
D --> E[Usage snapshot]
E --> F[Menu card meter]
E --> G[Inline dashboard]
Loading

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: The redacted terminal output proves after-fix decoding on a real key, but its zero-usage state renders the same result before and after; deterministic fixtures do not demonstrate the observed improvement in a real setup. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Resolve merge risk (P1) - Merging changes the reported remaining quota for existing OpenRouter keys; the available live run proves decoding but not the corrected result for a state where lifetime usage differs from the active limit window.
  • Complete next step (P2) - The remaining merge gate is contributor-supplied distinguishing real-behavior proof, not a concrete repair for an automation lane.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production versus test delta production +88/-18, tests +298 The four-file change updates both provider implementations and adds seven focused fixture cases to keep their quota calculations aligned.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #2605
Summary: This PR is the explicit candidate fix for the canonical OpenRouter monthly-limit report.

Members:

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

Merge-risk options

Maintainer options:

  1. Show a distinguishing quota result (recommended)
    Add redacted after-fix terminal or UI output for a key where server remaining differs from cumulative usage, then update the PR body for re-review.
  2. Accept the fixture-backed gap
    A maintainer may accept the deterministic Swift/JavaScript fixtures as sufficient despite the live account not exercising the changed calculation.

Technical review

Best possible solution:

Land the narrow provider fix after redacted after-fix CLI or UI evidence demonstrates a key whose server remaining differs from cumulative lifetime usage, or after a maintainer explicitly accepts the fixture-backed proof.

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

Yes, from source and the linked report: current main calculates remaining from cumulative key usage, while the supplied response has divergent cumulative and monthly values. The review did not execute a live provider probe.

Is this the best way to solve the issue?

Yes for the code path: server-reported remaining is the authoritative current-window value, with reset-window and cumulative fallbacks plus Swift/JavaScript parity coverage preventing drift.

AGENTS.md: found and applied where relevant.

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

Labels

Label justifications:

  • P2: This fixes incorrect OpenRouter provider quota presentation with a bounded impact on configured-key users.
  • merge-risk: 🚨 auth-provider: The PR changes how a provider API response determines the displayed key-limit meter for existing OpenRouter credentials.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The redacted terminal output proves after-fix decoding on a real key, but its zero-usage state renders the same result before and after; deterministic fixtures do not demonstrate the observed improvement in a real setup. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

  • Current main retains the reported behavior: Current main requires cumulative key usage and calculates both remaining dollars and percent directly from it; it has no server-remaining or reset-window selection path. (Sources/CodexBarCore/Providers/OpenRouter/OpenRouterUsageStats.swift:163, 9cc993f310cb)
  • PR implements the narrow source-order fix: The PR persists the server fields, uses finite server remaining first, then reset-window usage, then cumulative usage, and clamps exhausted or above-limit values. (Sources/CodexBarCore/Providers/OpenRouter/OpenRouterUsageStats.swift:145, e6155a03ac8a)
  • Swift and JavaScript paths receive fixture coverage: The PR adds four Swift quota fixtures and three Swift/JavaScript parity fixtures, including the monthly divergent response, missing cumulative usage, and above-limit server remaining. (Tests/CodexBarTests/ProviderPluginParityTests.swift:103, e6155a03ac8a)
  • The PR head is not on current main or a release tag: The PR base is contained in main but the current head is not; no release tag contains the head, so the fix remains an open landing candidate rather than an already-shipped change. (e6155a03ac8a)
  • Feature history identifies shared ownership: The current OpenRouter source and menu projection were recently maintained by Peter Steinberger, while Ratul Sarna carried earlier OpenRouter quota and resilience refinements. (Sources/CodexBarCore/Providers/OpenRouter/OpenRouterUsageStats.swift:133, 00ef23cdc9e4)
  • Provided live proof is non-distinguishing: The PR body shows a redacted real API response and after-fix CLI output, but its limit_remaining equals the limit and usage is zero; the author explicitly notes that this produces the same meter before and after the change. (e6155a03ac8a)

Likely related people:

  • steipete: Peter Steinberger authored the current declarative OpenRouter provider and menu projection architecture used by this change. (role: recent provider and menu-path contributor; confidence: high; commits: 00ef23cdc9e4, 698c332a250f; files: Sources/CodexBarCore/Providers/OpenRouter/OpenRouterUsageStats.swift, Sources/CodexBar/MenuCardView.swift)
  • Ratul Sarna: History shows repeated focused work on OpenRouter quota semantics, request resilience, and reset handling. (role: earlier OpenRouter quota contributor; confidence: medium; commits: 34a903941cc8, 9d6b32b8789f, 67de5f506143; files: Sources/CodexBarCore/Providers/OpenRouter/OpenRouterUsageStats.swift)

Rank-up moves

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

  • Add redacted after-fix terminal or UI output for a key where limit_remaining and lifetime usage produce different meter values.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (14 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-04T06:39:42.263Z sha 8e8fc36 :: needs changes before merge. :: [P2] Clamp JavaScript remaining to the configured key limit
  • reviewed 2026-08-04T07:12:53.996Z sha 8e8fc36 :: needs changes before merge. :: [P2] Clamp JavaScript remaining to the configured key limit
  • reviewed 2026-08-04T15:03:20.866Z sha 8e8fc36 :: needs changes before merge. :: [P2] Clamp JavaScript remaining to the configured key limit
  • reviewed 2026-08-04T16:39:03.945Z sha 8e8fc36 :: needs changes before merge. :: [P2] Clamp JavaScript remaining to the configured key limit
  • reviewed 2026-08-04T19:59:58.187Z sha 8e8fc36 :: needs changes before merge. :: [P2] Clamp JavaScript remaining to the configured key limit
  • reviewed 2026-08-05T02:27:30.154Z sha 3828e0f :: needs real behavior proof before merge. :: [P2] Validate the selected fallback quota value
  • reviewed 2026-08-05T02:49:43.469Z sha 827b57d :: needs real behavior proof before merge. :: [P2] Validate the selected fallback quota value
  • reviewed 2026-08-05T03:17:40.835Z sha e6155a0 :: needs real behavior proof before merge. :: none

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

Copy link
Copy Markdown
Contributor Author

Added redacted real-run proof to the PR body (live OpenRouter key response + codexbar usage --provider openrouter --format json --pretty on the PR head, no token or account identifiers) and fixed the negative limit_remaining exhausted-quota case with a regression test. @clawsweeper re-review

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

Copy link
Copy Markdown
Contributor Author

Updated the PR body with finite-limit real proof: live OpenRouter key response now reports limit: 500, limit_reset: "monthly", limit_remaining: 500, and the PR head renders the key-limit meter from server remaining (primary.usedPercent: 0). Redaction verified. @clawsweeper re-review

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 4, 2026
@Yuxin-Qiao
Yuxin-Qiao force-pushed the codex/fix-openrouter-monthly-limit-2605 branch from 76a47cf to 8e8fc36 Compare August 4, 2026 05:53
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and aligned the bundled JavaScript OpenRouter provider (openrouter.js) with the Swift quota path: server limit_remaining first, reset-window usage fallback, cumulative usage last, negative remaining clamped to zero. Added a Swift/JS parity fixture for the monthly-limit response. @clawsweeper re-review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e8fc3644e

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

function keyUsedForQuota() {
const limitRemaining = finite(keyData.limit_remaining, "key.limit_remaining", true);
if (limitRemaining !== null) {
return keyLimit - Math.max(0, limitRemaining);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clamp server remaining to the key limit

When the JavaScript provider is enabled and OpenRouter reports limit_remaining slightly above limit (for example, after a quota adjustment or due to rounding), this subtraction produces a negative used value. The used >= 0 check below then omits the primary meter entirely, while the Swift path clamps remaining to the limit and renders 0% used. Clamp limitRemaining to [0, keyLimit] before subtracting so the two provider paths remain consistent.

Useful? React with 👍 / 👎.

@johannrymill

Copy link
Copy Markdown

Nice, thanks for addressing this so quickly.

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.
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Aug 5, 2026
@Yuxin-Qiao

Copy link
Copy Markdown
Contributor Author

Addressed the fallback-validation finding: hasValidKeyQuota now validates the selected fallback quota value, so a reset-window response renders the meter even without cumulative usage (Swift now matches the JS provider). Added a Swift fetch fixture and a Swift/JS parity fixture. Also documented why the live zero-usage key cannot produce a distinguishing meter and that the divergent state is covered by the monthly fixture (86.6% vs 9.09%). @clawsweeper re-review

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 5, 2026
@steipete
steipete merged commit cca00ab into steipete:main Aug 5, 2026
9 checks passed
@steipete

steipete commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Landed. Verification before merge (independent maintainer-agent review):

  • Core Swift change verified: the key meter now prefers server limit_remaining clamped to [0, keyLimit], falling back to the limit_reset-matching window usage, then cumulative usage. The "left" dollar text and the meter derive from the same clamped remaining, so they cannot disagree; negative/overspent remaining renders $0.00 left and 100% used consistently.
  • Defect found and fixed on the branch (3828e0fe3): the bundled JS provider clamped remaining only at zero, so a finite limit_remaining above limit produced a negative used amount and silently suppressed the meter while Swift showed 0% used. Fixed with a symmetric clamp plus a new above-limit Swift/JS parity fixture that fails without the fix — this was also ClawSweeper's open P2 blocker on the linked issue.
  • Local: swift test --filter 'ProviderPluginParityTests|OpenRouterUsageStatsTests' — 18/18 pass; make check — 0 violations. CI green at merge.

Fixes #2605.

Finesssee added a commit to nesszer/Win-CodexBar that referenced this pull request Aug 9, 2026
* 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenRouter monthly key limit uses cumulative usage instead of current-period remaining

3 participants