Skip to content

fix(serve): bound the whole request head, not just each read - #2684

Merged
steipete merged 3 commits into
steipete:mainfrom
OfficialAbhinavSingh:fix/serve-request-deadline
Aug 6, 2026
Merged

fix(serve): bound the whole request head, not just each read#2684
steipete merged 3 commits into
steipete:mainfrom
OfficialAbhinavSingh:fix/serve-request-deadline

Conversation

@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor

Closes #2683.

Problem

readRequest bounds each recv with requestReadTimeoutMilliseconds (5s) but nothing bounds the request head as a whole:

while data.count < 16384 {
    guard waitForReadable(fd, timeoutMilliseconds: requestReadTimeoutMilliseconds) else {
        return .failure(.invalidRequest)
    }
    ...
}

A client that keeps trickling bytes just inside that window never trips the timeout, so it holds its connection — and the cooperative-executor thread serving it — for as long as it keeps sending.

This is pre-auth: the Host allowlist and the bearer-token check both run only after the head has been read. And over-cap connections are not queued — connectionGate.tryAcquire() fails and the socket is closed immediately — so legitimate clients get dropped outright rather than waiting their turn.

Worth noting: a client that simply goes silent is already handled correctly by the per-read timeout. The gap is specifically the client that keeps sending.

Fix

Track a monotonic start time, cap each wait at the remaining budget, and fail the request once the overall deadline passes. 10s ceiling, generous for a legitimate head but bounded.

Proof (Linux, swift:6.3.3)

The test boots the real CLILocalHTTPServer on an ephemeral port, opens maximumConnections sockets that each send one header byte per second and never terminate the header block, then retries a well-behaved GET /health against a 25s budget.

Before:

✘ a well-behaved client never got a connection slot within 25s;
  trickling clients held every slot because the request head has no overall deadline
  (failed after 25.776 seconds)

After:

✔ trickling clients cannot hold connection slots indefinitely (passed after 10.260 seconds)

The 10.26s matches the deadline exactly. I reverted the fix and re-ran to confirm the test genuinely fails without it, rather than trusting a green run.

Full Linux suite 356/356, swiftformat --lint clean, swiftlint --strict 0 violations / 1814 files, and Scripts/regenerate-codex-parser-hash.sh check is current.

Scope

Default bind is loopback, so in practice this is a local user wedging the daemon; it becomes network-reachable only with --host 0.0.0.0, which already demands --allow-plain-http and a token. I treated it as robustness hardening rather than a privilege-boundary issue — happy to be corrected on that framing.

Deliberately out of scope

sendResponse is likewise a blocking send loop with no SO_SNDTIMEO, so a client that never drains its receive window can stall a writer. Same class, separate fix; left out to keep this diff small. Happy to follow up if you want it.

`readRequest` applied `requestReadTimeoutMilliseconds` per `recv` with no overall
limit, so a client trickling one byte just inside that window never timed out and
held its connection — and the cooperative-executor thread serving it — for as long
as it kept sending. The Host allowlist and bearer-token check both run after the
head is read, and over-cap connections are closed rather than queued, so a few such
clients denied service to well-behaved ones entirely pre-auth.

Track a monotonic start time and cap each wait at the remaining budget, failing the
request once the overall deadline passes. A client that merely goes silent was
already handled by the per-read timeout; this covers the one that keeps sending.

Verified red→green on Linux: before, a legitimate client never got a slot within a
25s budget; after, it is served at 10.26s, matching the deadline.

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

ℹ️ 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 +3 to +6
#if canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard the syscall test from macOS builds

When macOS Swift tests run for this non-doc change, CodexBarLinuxTests is still declared unconditionally in Package.swift, so this new source file is compiled on macOS too. This import block has no Darwin branch, and the code below uses the Linux-only SOCK_STREAM.rawValue form; on the macOS compile path that fails before any tests can run. Please wrap the file in #if os(Linux) like the other Linux syscall tests or add a proper Darwin socket branch.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks — that's a real break, not a flake, and I should have caught it.

TestsLinux is declared unconditionally in Package.swift, so the file compiled on macOS where SOCK_STREAM.rawValue and the Glibc imports don't exist — both shards failed before any test ran. My local pipeline is Linux-in-Docker, so it couldn't see this.
Fixed in 009dbbf: wrapped the file in #if canImport(Glibc) || canImport(Musl), matching AntigravityProcessLauncherLinuxTests and the other syscall suites in that directory rather than inventing a new pattern.
Re-verified after the change — Linux suite still passes (10.26s, matching the 10s deadline), swiftformat --lint clean, swiftlint --strict 0 violations / 1814 files.

`TestsLinux` is declared unconditionally in Package.swift, so this file also
compiles on macOS, where the raw socket calls (`SOCK_STREAM.rawValue`, Glibc-only
imports) do not build and broke both macOS test shards.

Wrap the file in `#if canImport(Glibc) || canImport(Musl)`, matching
AntigravityProcessLauncherLinuxTests and the other syscall suites here.
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 5, 2026
@clawsweeper

clawsweeper Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codex review: needs changes before merge. Reviewed August 5, 2026, 9:56 PM ET / August 6, 2026, 01:56 UTC.

ClawSweeper review

What this changes

The PR adds a total deadline for one codexbar serve HTTP request head and a Linux socket regression test for clients that continuously trickle incomplete headers.

Merge readiness

⚠️ Needs maintainer review before merge - 3 items remain

Keep open: the unchanged PR head still starts the total deadline only when its client task receives an executor worker, after the connection slot is acquired. Capture the deadline at acceptance and add a saturated-queue regression test before merge.

Likely related people: steipete — initial local HTTP server author (high confidence).

Priority: P2
Reviewed head: 3e560206f87f06366395b07d05a42b2887e606a8

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) Real behavior proof is strong, but the remaining acceptance-time deadline defect prevents merge readiness.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.
Patch quality 🦐 gold shrimp (3/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.
Evidence reviewed 5 items Current server lifecycle: Current main acquires the connection slot before creating the client task; the task then enters the request-reading path. A deadline initialized inside that path excludes time spent waiting for a cooperative-executor worker.
Unfixed prior blocker: The supplied prior review raised this exact P2 concern on head 3e56020; the current PR metadata names the same head SHA, so no commit has addressed it.
Feature provenance: Blame attributes the connection gate, accept loop, task creation, and reader lifecycle to the original local-server implementation commit.
Findings 1 actionable finding [P2] Start the deadline before scheduling the client task
Security None None.

How this fits together

CodexBar’s local HTTP server accepts CLI health and control requests, limits concurrent connections, then parses and validates request headers before dispatching the handler. This change bounds pre-auth header reading so incomplete requests release a connection slot.

flowchart LR
    A[Client socket] --> B[Connection slot gate]
    B --> C[Client task]
    C --> D[Request-head reader]
    D --> E{Complete before deadline?}
    E -->|Yes| F[Host and token validation]
    E -->|No| G[Close socket and release slot]
    F --> H[HTTP handler response]
Loading

Before merge

  • Start the deadline before scheduling the client task (P2) - The connection slot is acquired before Task is created, but the new clock starts only after that task gets a cooperative-executor worker. Accepted trickling sockets queued behind blocked readers therefore retain slots without spending their budget, allowing successive full deadline windows; capture an absolute deadline here and pass it into request reading. This is the still-unfixed prior review finding on the unchanged head.
  • Resolve merge risk (P1) - A saturated cooperative executor can leave accepted trickling sockets holding connection-gate slots for successive full deadline windows, so legitimate clients may still be rejected for longer than the advertised ten-second bound.
  • Complete next step (P2) - The remaining blocker is a narrow mechanical deadline-propagation repair with a clear source boundary and regression scenario.

Findings

  • [P2] Start the deadline before scheduling the client task — Sources/CodexBarCLI/CLILocalHTTPServer.swift:401-406
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test delta production +37/-4, tests +198 The focused Linux integration test is substantial coverage for a small server-lifecycle change, but it does not cover the queued-task timing path.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #2683
Summary: This PR is the explicitly linked candidate fix for the request-head deadline bug.

Members:

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

Merge-risk options

Maintainer options:

  1. Bound accepted sockets from acceptance time (recommended)
    Capture a monotonic deadline before creating the client task, pass it to request reading, and add a saturation test that verifies queued accepted sockets cannot retain slots beyond that deadline.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Capture the request-head deadline before client-task creation, pass the absolute deadline through the reader, and add a saturated executor/connection-gate regression test proving slots release within one budget.

Technical review

Best possible solution:

Make the request-head deadline run from socket acceptance through header completion, and prove that queued accepted clients release their slots within that single budget.

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

Yes—current-main source shows slots are acquired before task scheduling, and the supplied real-server Linux transcript establishes the slow-trickle failure mode; this review did not execute tests under the read-only contract.

Is this the best way to solve the issue?

No—the total-read deadline is the right narrow fix, but it must start at socket acceptance rather than when a delayed client task begins reading.

Full review comments:

  • [P2] Start the deadline before scheduling the client task — Sources/CodexBarCLI/CLILocalHTTPServer.swift:401-406
    The connection slot is acquired before Task is created, but the new clock starts only after that task gets a cooperative-executor worker. Accepted trickling sockets queued behind blocked readers therefore retain slots without spending their budget, allowing successive full deadline windows; capture an absolute deadline here and pass it into request reading. This is the still-unfixed prior review finding on the unchanged head.
    Confidence: 0.97

Overall correctness: patch is incorrect
Overall confidence: 0.95

AGENTS.md: found and applied where relevant.

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

Labels

Label justifications:

  • P2: The remaining bounded-connection failure affects codexbar serve availability but is a limited-scope reliability defect.
  • merge-risk: 🚨 availability: Merging without an acceptance-time deadline can leave connection slots occupied beyond the intended bound under executor saturation.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.

Evidence

Acceptance criteria:

  • [P1] swift test --filter CLIServeRequestDeadlineLinuxTests.
  • [P1] make test.
  • [P1] make check.

What I checked:

Likely related people:

  • steipete: Current-main blame attributes the connection gate, accept loop, client task, and request reader to this implementation commit. (role: initial server implementation author; confidence: high; commits: 6afa6728f3c4; files: Sources/CodexBarCLI/CLILocalHTTPServer.swift)

Rank-up moves

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

  • Capture the absolute deadline before task creation and pass it to the request reader.
  • Extend the Linux regression to cover queued accepted clients under executor saturation, then rerun the focused test and repository checks.

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 (4 earlier review cycles)
  • reviewed 2026-08-05T19:55:20.664Z sha 009dbbf :: needs maintainer review before merge. :: none
  • reviewed 2026-08-05T20:20:44.448Z sha 009dbbf :: needs maintainer review before merge. :: none
  • reviewed 2026-08-05T20:26:39.928Z sha 3e56020 :: needs changes before merge. :: [P2] Start the deadline when the socket is accepted
  • reviewed 2026-08-06T00:03:41.885Z sha 3e56020 :: needs changes before merge. :: [P2] Start the deadline when the socket is accepted

The deadline test held executor threads for the full 10s production budget, which
starved unrelated suites: CLIHooksWatchSleepLinuxTests asserts a stop signal is
honored within 2s and instead measured 10.31s on CI. `.serialized` only orders
cases within a suite, so it does not prevent that.

Make the budget injectable on CLILocalHTTPServer, defaulting to the production
value, and have the test pass 1500ms. Full suite drops from ~10.5s to ~2.0s.

Verified the test still fails when the deadline logic is disabled (12.37s
starvation), so the shorter budget did not weaken it, and ran the full suite 8x
with no flake.
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 5, 2026
@steipete
steipete merged commit 9749b99 into steipete:main Aug 6, 2026
9 checks passed
@steipete

steipete commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Landed. Verified before merge: bound math checked (monotonic clock, wraparound-safe elapsed, per-poll wait = min(5s, remaining), 16 KB + 10 s total regardless of trickle rate); Host allowlist and bearer auth confirmed to run only after the head read, so this closes the pre-auth slowloris hold. Status mapping stays within the server’s existing 400/403 vocabulary — right call for a loopback daemon. The Linux trickle test is genuinely adversarial and correctly platform-gated. Local: 89 macOS CLIServe tests pass on the branch; PR CI fully green incl. both Linux builds. Thanks @OfficialAbhinavSingh!

proxynico added a commit to proxynico/CodexBar that referenced this pull request Aug 14, 2026
* fix: use in-process libproc instead of ps/lsof scans (#2599)

Replace repeated macOS process-inspection subprocesses with scoped libproc and sysctl calls while preserving the Linux ps, lsof, and /proc paths. Add defensive PROCARGS2 parsing and self-process integration coverage to harden #2267.

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* fix: size provider menus to their content (#2602)

* build: refresh widget dependency lockfile (#2603)

* build: sync widget Package.resolved with root (SweetCookieKit 0.5.1) (#2604)

Root Package.resolved moved ahead of the widget workspace copy, so
Scripts/package_app.sh release failed at the widget step with an
out-of-date resolved file error. The script's failure handling was
verified correct (non-zero exit, no stale bundle produced).

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor: derive provider registrations from descriptors (#2606)

* refactor: consolidate provider bootstrap manifests

* refactor: derive provider icon styles from descriptors

* refactor: derive provider ancillary registrations

* refactor: derive widget provider metadata

* test: enforce provider widget metadata sync

* docs: simplify provider authoring workflow

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* fix: make iCloud sync survive first contact with a real fleet (#2608)

Four fixes from live multi-Mac testing:
- CKRecord rebase crash: allKeys() includes encrypted field names; routing
  them through the plain subscript threw NSInvalidArgumentException and
  crash-looped every receiver applying fetched records (regression test).
- Fetch-before-push on first sync: a fresh device now applies fleet state
  before composing pushes, so its editCount-1 records can't win conflict
  ties and clobber the fleet.
- Persist remote applies: applyExternalConfig skips disk writes by design
  (reload path); sync applies now schedulePersistConfig so config.json,
  the CLI, and the next launch see the merged result.
- Dirty-set push gating: startup no longer wholesale-uploads local config
  (a relaunched stale Mac degraded the richest Mac's config twice in
  testing). Providers push only when locally edited (persisted dirty set,
  cleared on save success), with an empty-fleet bootstrap seeding path.

Verified live: three-Mac convergence (golden profile incl. E2E-encrypted
secrets propagated to two receivers; source unchanged; fleet devices and
snapshots visible everywhere; no-account Mac degrades gracefully).

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(alibaba-token-plan): personal requests rejected with Workspace.NotAuthorised (hardcoded switchAgent) (#2533)

* fix(alibaba-token-plan): personal requests rejected with Workspace.NotAuthorised

The Personal/Solo path hardcoded `switchAgent: 1_233_135` in the
`cornerstoneParam` request body. The gateway binds that value to a specific
account's workspace, so for any other account the call is rejected with
`BailianGateway.Workspace.NotAuthorised` even though the outer envelope
claims `code: "200"`. Omitting the field lets the gateway resolve the
session's default workspace.

Also:
- Resolve `sec_token` best-effort for personal requests (the browser always
  sends it; some accounts are rejected without it) and append it to the body
  when available, mirroring the Teams path.
- `throwIfErrorPayload` now reads `errorCode`/`errorMsg` from the nested
  frame that carries `success: false`, so the real gateway error is surfaced
  instead of a misleading "API error: 200". Authorization-family errors map
  to `invalidCredentials` so the UI prompts for re-authentication.

Fixes #2500. Verified live against the mainland Personal/Solo API and covered
by new regression tests.

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

* fix: preserve Alibaba personal sessions

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* Add Notion AI provider (#2552)

* Add Notion AI provider

Tracks the two usage-allowance windows Notion shows in Settings > Notion AI >
Usage: the rolling 6-hour window and the billing-period window. Notion begins
enforcing the allowance on 2026-08-03; before then the same endpoint already
returns real numbers with "enforcement": "preview".

Reads two cookie-authenticated endpoints on app.notion.com that the Notion web
app itself calls: getSpaces for account identity and workspace plans, and
getCreditRateLimitStatus for the allowance. Only Business and Enterprise
workspaces carry one; anything else reports not_applicable as a clear error
rather than an empty gauge.

The imported cookie header is persisted through CookieHeaderCache. Chromium
cookie reads are gated to user-initiated refreshes to avoid a Keychain prompt,
so without a cached header the provider fails on every background refresh and
reports "no cookies found" while the session is valid. Cookies are also
de-duplicated by name across the Notion domains, since a stale token_v2 left on
the legacy notion.so alongside the live one would otherwise send both.

Every field of the rate-limit response is optional, so an unrelated 200 body
decodes cleanly into an all-nil status. The parser rejects a payload carrying
neither window, and a window with no usable limit is omitted rather than
reported as 0% used.

Adding the .notion debug-log case tipped UsageStore.debugLogText past the
cyclomatic-complexity cap, so .openai and .azureopenai are folded into one case;
both already call the same apiKeyDebugLine helper.

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

* fix(notion): harden cookie session handling

* test: isolate widget snapshot persistence

* feat(notion): pace both allowance bars and honor CLI provider settings

The billing-period window is scored against the real calendar month ending
at its reset rather than a flat 30 days, and the rolling window gets the
session-pace treatment on both the card and in `codexbar usage`.

Notion reports only `periodEndMs` for the billing period, so the snapshot
carries the shared monthly sentinel and the descriptor declares
`.calendarMonthResetWindow`. Resolution then substitutes the true cycle
length. Three paths scored these windows without resolving first --
`UsageStore.weeklyPace` (menu-bar pace token, "runs out" text, predictive
pace warnings) and `resetWindowPaceDetail` when handed a precomputed pace
-- so a February cycle read 6% expected at 0% used and a 31-day cycle lost
its pace token for a day. Both now resolve, which also fixes the eight
other providers that carry the same sentinel.

The CLI ignored the Notion settings snapshot entirely, so Workspace ID, a
manual cookie header, and the `off` source had no effect on `codexbar`.

Also drops a rolling window length that parses to exactly the monthly
sentinel (`30d`, `720h`, `43200m`), which would otherwise be resolved as a
calendar cycle ending hours from now.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* release: 0.47.0

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

* fix: make widget ProviderChoice display metadata a static literal

The AppIntents metadata processor in current Xcode rejects computed
caseDisplayRepresentations (must be a literal, exhaustive dictionary),
which broke the 0.47.0 release build. Titles are pinned to the
descriptor registry by WidgetProviderChoiceTests so providers cannot
drift silently.

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

* docs: update appcast for 0.47.0

* docs: open 0.47.1 Unreleased

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

* Add scoped-weekly (Fable) percentage as a menu-bar layout token (#2440)

* Add scoped-weekly (Fable) percentage as a menu-bar layout token

Claude's model-scoped weekly carve-outs (the "Fable only" window from #1851)
already show in the dropdown and in quota notifications, but the always-visible
menu bar could only show session, weekly, or automatic. This adds a scoped-weekly
token so the promo-window limit can sit in the bar.

- New PercentWindow.scopedWeekly, shown with an "F" prefix, selectable in the
  layout editor (palette, label, live and representative previews).
- scopedWeeklyWindow resolver matches the claude-weekly-scoped-* id prefix rather
  than a model name; when several are active it shows the most constrained one.
- The scoped-weekly percent is added to the icon-observation signature so a
  scoped-only change refreshes the title, mirroring weekly= and avoiding the
  stale-title problem from #2300 and #2299.
- Absent window renders the standard "–" placeholder; editor label localized.

Addresses #2360.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address review: label scoped-weekly token by active model

Codex flagged that the token hard-coded "F" / "Fable only" while the resolver
accepts any claude-weekly-scoped-* window and picks the most constrained, so a
non-Fable window could be shown under the wrong model label.

- scopedWeeklyNamedWindow now returns the NamedRateWindow.
- The .scopedWeekly token derives its prefix from the active window's title
  (first letter) and its accessibility text from the full title, instead of a
  fixed "F" / "Fable only".
- Carry the title through MenuBarLayoutRenderData and include it in the
  icon-observation signature, so a model change refreshes the title.
- Resolver test now asserts a non-Fable most-constrained window carries its title.

The editor palette label stays "Fable %" (a proper noun that reads the same across
locales); a generic name is left as a naming choice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add menu bar pace layout tokens

The 0.45 layout editor replaced the old Percent/Pace/Both display modes,
but no token exposes the signed pace delta the Both mode used to render.
`Runs out` is not a substitute: it answers when a window ends, always
estimating from the weekly (or automatic) lane, while pace answers how far
off the sustainable rate usage currently runs.

Add Session/Weekly/Auto pace tokens that mirror the percent tokens' window
selection and reuse the existing `MenuBarDisplayText.paceText` formatting
(`+11%` ahead of the rate, `-8%` behind, `0%` on pace). Each token resolves
pace for its own window, so Weekly pace never borrows the session delta.

Pace needs the store's historical dataset and work-day setting, so it is
resolved upstream like `runsOut` through a shared `menuBarLayoutPaceText`
helper that both the status item and the editor preview call. The existing
3% expected-usage floor in `weeklyPace` still applies, so a token renders
the en-dash placeholder early in a window while its siblings stay visible.

Refs #2534

* Include layout pace tokens in the icon observation signature

Pace values change with the historical dataset, the work-day setting,
and the clock, none of which move the percent fields already hashed by
providerStoreIconObservationSignature. A historicalPaceRevision bump
therefore woke the icon observer but produced an unchanged signature,
so updateIcons() was skipped and a custom pace token kept its stale
value until an unrelated icon change forced a redraw.

Contribute the active layout's pace values to the signature the same
way cost and account tokens already do, gated on the layout actually
containing a pace token. The regression test renders two snapshots with
identical used percents but different resets: without this fix both
signatures were identical.

Refs #2534

* docs: credit menu bar pace contributor

* fix: localize scoped weekly label

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Petr Kratochvíl <krato@krato.cz>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* Add one-shot dashboard snapshot command (#2499)

* Add one-shot dashboard snapshot command

* Fix dashboard pricing refresh policy

* Honor Cursor source policy in dashboard

* test: dedupe cliExecutableURL helper after branch update

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

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat: JS provider plugin runtime prototype (behind debug flag) (#2617)

* feat: add provider plugin runtime

* feat: convert synthetic venice and crof to JS plugins

* test: prove JS provider parity

* docs: document provider plugin prototype

* fix: satisfy plugin runtime lint checks

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* fix: sync CLI config edits via iCloud (#2618)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* test: make widget snapshot storage hermetic (#2619)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* feat(menu): compact usage detail rows (#2620)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Shun Min Chang <ji394m6y7@gmail.com>

* feat: add China Kimi and GLM quota routing (#2621)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: haoli <haoli@local.dev>

* feat: enrich Kimi monthly usage from desktop (#2622)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: haoli <haoli@local.dev>

* feat: declarative provider detail sections + JS detail providers (#2624)

* feat: add declarative provider detail model

* feat: render declarative provider details

* feat: bridge provider details from JavaScript

* feat: convert detail providers to JavaScript

* style: satisfy provider detail test lint

* fix: align z.ai plugin quota lanes

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* Unify pi and OMP agent sessions (#2626)

* Add live OhMyPi agent session discovery

Detect running OhMyPi harness processes, correlate them with bounded session metadata, and expose normalized sessions through the existing CLI and menu paths.

* Address OhMyPi review correlation gaps

Resolve OhMyPi metadata per live process environment, reject stale records, and isolate directory scan budget so existing provider discovery remains intact. Add focused regression coverage and document the fail-closed behavior.

* Preserve existing session metadata during OhMyPi scans

Give OhMyPi its own bounded directory budget so Codex and Claude correlation remain available, with regression coverage for the shared-provider behavior.

* Preserve legacy session JSON compatibility

Add an explicit v2 session payload for OhMyPi while keeping legacy remote clients decodable, and document and test the negotiated fallback.

* Expose OhMyPi in local session JSON

Keep the documented local --json output complete while preserving the remote v2-first compatibility fallback for older installations. Update CLI help, focused protocol coverage, and session documentation to distinguish current local output from legacy remote responses.

* fix(sessions): preserve legacy JSON compatibility

* Correct session JSON protocol overview

* feat(sessions): unify Pi-family discovery

Co-authored-by: William Mitchell <wdmitchell.uk@gmail.com>

---------

Co-authored-by: William Mitchell <wdmitchell.uk@gmail.com>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* fix(zoommate): preserve browser cookie scope (#2627)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* Keep cached spend visible while Codex refreshes (#2628)

* fix: show cached spend while refreshing

Co-authored-by: hhh2210 <hzy2210@gmail.com>

* test: add cached spend refresh proof

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: hhh2210 <hzy2210@gmail.com>

* feat: route GLM credentials by region (#2623)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: haoli <haoli@local.dev>

* Define Codex Fast cost as API Fast USD (#2632)

* Centralize Codex priority pricing

* fix: define Codex Fast USD pricing

* test: split pricing expectation math for CI type-checker

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

---------

Co-authored-by: Bryan Font <bfont@me.com>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix: parse Command Code usage windows (#2630)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Derek Zeng <zengzhuoxi@gmail.com>

* feat: plugin cookie capability and host API extensions

* feat: extend plugin host and convert providers

* fix: align z.ai plugin credential routing

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: introduce ProviderInstanceID identity seam (#2636)

* test: characterize provider instance identity

* feat: add provider instance identity seam

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* feat: user-installed provider plugins with TypeScript support (#2642)

* feat: add user-installed provider plugins

* fix: broker owns plugin HTTP representation headers

Apply broker-owned Accept, Accept-Encoding, and Content-Type after
plugin-supplied headers so a plugin cannot relax the user-plugin
response boundary. Test transport gains configurable response headers.

* build: add reproducible sucrase bundle verification script

Regenerates and verifies Sources/CodexBarCore/Resources/Plugins/
sucrase-3.35.1.min.js from the official npm artifact (sucrase@3.35.1,
esbuild@0.25.8 pinned, IIFE browser bundle). Expected SHA-256
4d997e15b72cbc9ccf6e743c30c6eb48bf4533f6709852367b40766be5eba70b was
independently reproduced by the coordinator from the npm registry;
'check' mode fails closed on any mismatch.

* fix: gate user-plugin registry lookup for non-JavaScriptCore platforms

Linux CLI builds compile CodexBarConfig without the plugin runtime;
unknown plugin config entries are dropped with the existing warning,
matching the documented macOS-only plugin boundary.

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* test: deflake Kimi grace budget timing (#2657)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* test: keep widget-snapshot container I/O out of test runs (#2656)

* test: keep widget-snapshot container I/O out of test runs

AdaptiveRefreshHeuristicsTests (and any suite reaching persistWidgetSnapshot without a save override) could hang forever on macOS 26 hosts: WidgetSnapshotStore.load() opens the real app-group container file and the open() can block indefinitely behind app-data (TCC) gating. Gate persistWidgetSnapshot so test runs only persist when a test explicitly installs _test_widgetSnapshotSaveOverride, and pin the behavior with regression tests.

* test: open widget-snapshot gate for injected snapshot URLs

Main's plugin series added a widgetSnapshotURL injection seam that promotion tests rely on; the gate now admits it alongside the save override and replaces the duplicate inline DEBUG guard.

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* feat: add shared-cache token activity heatmap (#2625)

* feat: add standalone token activity heatmap

* fix: preserve token activity coverage

* fix: complete activity heatmap navigation

* fix: expose daily activity accessibility

* fix: publish daily accessibility semantics

* fix: expose dates in activity accessibility

* fix: expose weekly activity accessibility

* feat: derive token activity from shared cache

---------

Co-authored-by: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* Expose Codex scan completeness in cost JSON (#2520)

* Expose provider-native cost scans

* feat(cli): expose Codex cost scan completeness

Co-authored-by: NickGuAI <yu.gu.columbia@gmail.com>

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: refresh Plugins pane visuals to match sibling settings panes (#2662)

* feat: resizable settings sidebar with persisted width (#2660)

* feat: resizable settings sidebar with persisted width

Replace the fixed 260pt settings sidebar with a drag-resizable one
(200-380pt, persisted via AppStorage). The divider stays the original
SwiftUI hairline; an input-only AppKit strip overlaid on the detail
pane's leading edge handles dragging, so the HStack layout is untouched.
Hover cursor via .pointerStyle(.columnResize) on macOS 15+ plus an
explicit-rect tracking area (visibleRect spans the whole window under
NSHostingView, so .inVisibleRect never fired).

* fix: hold the sidebar resize cursor with a cursor rect

The hover cursor was set imperatively on mouse enter/move, which SwiftUI
undid on its next render — the settings panes re-render continuously, so
the resize cursor only flashed. Cursor rects are declarative: AppKit
re-asks the view on every invalidation, so the cursor holds.

Measured on a probe harness hosting this view under a 20Hz re-render
loop, sampling NSCursor.currentSystem at 25Hz while an external driver
moved the pointer: imperative-only gave 0% resize on hover and 40%
during drag; with cursor rects, two clean transitions (arrow -> resize
on entry, back on exit) and 100% held across hover and drag.

* style: fix SwiftFormat violations in SidebarResizeHandle

Mechanical ./Scripts/lint.sh format output; #2660 landed with docComments
and wrap-body violations that broke CI on main.

* Wait for OpenCode Go Zen balance in CLI usage reads (#2583)

* Wait for OpenCode Go Zen balance in CLI usage reads

* fix: bound OpenCode Go CLI balance lookup

* Keep OpenCode Go Zen wait scoped to usage reads

* Measure OpenCode Go Zen wait from task start

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* OpenCode Go: per-model cost breakdown by day (#2649)

* feat: per-model cost breakdown for OpenCode Go's daily cost history

Extract each local assistant message's modelID from opencode.db (the
real model behind the constant opencode-go Zen-proxy providerID) and
group cost/request counts by (day, model) instead of just by day, so
the shared Cost history chart shows a per-model breakdown for
OpenCode Go the same way it already does for Claude/Codex. Rows with
no modelID fall back to an "unknown" bucket instead of being dropped.

* fix: use lowercase docs/claude.md link to pass case-sensitive CI lint

macOS resolves docs/CLAUDE.md to the same file as docs/claude.md, but
the Linux lint runner's case-sensitive filesystem doesn't, failing the
documentation-link check.

* fix: normalize the trimmed model id when grouping OpenCode Go costs

The grouping key used the untrimmed modelID even though emptiness was
checked against the trimmed value, so a model id with incidental
leading/trailing whitespace would form its own bucket instead of
merging with the clean value for the same model. Group by the trimmed
value consistently, and add regression tests for whitespace-only and
whitespace-padded model ids.

* Fix ETXTBSY race in claude-swap CLI card test (#2644)

* Fix ETXTBSY race in claude-swap CLI card test

The test wrote a shell script, set mode 0755, and executed it immediately.
Under swift test --parallel a concurrent fork inherits the still-open write
descriptor, so execve fails with ETXTBSY and the launch reports Cocoa 256.

Write the script body as data and execute a checked-in trampoline that reads
it, so execve only ever touches a file no test process has written.

Measured in swift:6.3.3-noble on arm64: 112 failures in 600 attempts with the
old shape, 0 in 600 with the trampoline.

* Apply ETXTBSY fix to the Linux test target

The first commit only touched Tests/CodexBarTests, which Package.swift builds
on macOS alone. The Linux job compiles CodexBarLinuxTests from TestsLinux, so
the race remained in the target that actually flaked.

Convert all three TestsLinux sites through a shared FakeExecutable helper, and
keep the macOS mirror in step.

Verified on Linux arm64 in swift:6.3.3-noble: 355 tests in 53 suites pass.

* Pin Perplexity promo expiry formatter to en_US_POSIX (#2651)

* Pin Perplexity promo expiry formatter to en_US_POSIX

The formatter set dateFormat but not locale, so MMM rendered in the user's
language inside an otherwise English string and diverged from the JS plugin
projection. Matches the en_US_POSIX pinning already used by every other
provider formatter.

Refs #868

* Assert the Perplexity promo expiry locale pin in tests

Parity coverage only trips on a non-English host, so an English runner
passes both before and after the regression. Add assertions that hold on
every host: the formatter keeps its en_US_POSIX locale, and the rendered
month stays ASCII English.

* Move the promo expiry fixture off the UTC month boundary

The formatter keeps the host time zone, so a midnight-UTC January instant
renders as Dec 31 west of UTC and failed the month assertion there even
with the locale pin correct.

* Decode Copilot credits_used for token-billed seats (#2593) (#2613)

* Decode Copilot token-billing credits used

* Cover credits used counter in token-billing card regression

* Keep Copilot credits counter accessible through snapshots

* Preserve Copilot credits counter across quota fallback

* Expose Copilot credits counter in diagnostics

* Keep credits counter on zero entitlement quota fallback

* fix: keep Codex fork catch-up progress across appends (#2648)

* fix: keep Codex fork catch-up progress across appends

* Keep subagent fork appends on full rescan

* Preserve zero-work subagent retries

* Report unreadable Claude OAuth refresh as terminal (#2650)

* Report unreadable Claude OAuth refresh as terminal

When the delegated Claude CLI touch completes but the Keychain fingerprint
does not move, the attempt reported a retryable failure. With foreign
Keychain reads disabled in release and no credentials file for the profile,
that state cannot be retried out of: the refreshed credential is
unreadable no matter how often the touch runs.

Add Outcome.unreadableAfterRefresh for that case, back off on the long
cooldown instead of the short one, and drop the 'run claude login, then
retry' advice that cannot help. The touch still runs first, so older Claude
Code versions that write the credentials file keep recovering.

Refs #2634

* Keep the delegated refresh Outcome contract public-stable

CodexBarCore exports Outcome through its library product, so the added
case would break downstream exhaustive switches on source update. Move the
unreadable verdict onto an internal AttemptResult alongside the outcome and
restore the original public cases; the fetcher reads the detail through
attemptDetailed.

* Cover retryable touch failure in unreadable configuration

The touch-error path staying retryable (short cooldown, no terminal
verdict) is deliberate: the error may be transient, and on older Claude
Code a retried touch can still create the credentials file. Document
that at the decision site and lock it in with a regression test so the
SessionError variant from #2634 has explicit coverage.

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test: stabilize PTY overflow proof (#2664)

* Classify Codex rate windows by duration (5h/weekly/30-day) (#2600)

* Classify Codex rate windows by duration

* Keep monthly Codex history visible in the chart

* Suppress session pace for non-session Codex windows

* Preserve monthly Codex windows in reset backfill and keep fallback session pace

- Reset backfill rebuilt raw slots only from the session/weekly lanes, so a
  43,200-minute primary (now classified .monthly) was dropped or overwritten
  by a stale cached session window whenever a trusted backfill baseline
  existed. Preserve monthly-classified slot windows in place and backfill
  their reset from a matching cached monthly window.
- The session-pace guard rejected every non-300-minute Codex window, removing
  existing pace for fallback session durations (e.g. 540 minutes or nil).
  Suppress pace only for windows that classify into the weekly/monthly lanes,
  and let the central guard drive the card instead of exact-duration gates.
- Move the Codex reset-backfill helpers into their own file (file-length lint).
- Regression tests for both paths; they fail without the fixes.

* Retain monthly reset baselines in codex backfill merge

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Retain priced Codex models when Auto Review is unpriced (#2643)

* Retain partial Codex model rows

* Use safe Codex dashboard test fixture

* Fail closed for malformed Codex model costs

* Add regression test for fully unpriced Codex routing history

* Extract model breakdown helpers to satisfy type body length

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Bound Codex cost cache persistence size (#2637) (#2646)

* Bound Codex cost cache persistence size

* Preserve resumable and fork-dependent cache entries

* Prune cache against requested window

* Prune candidate artifacts over byte budget

* Scope cache guards and preserve active sessions

* Keep cache loadable under byte budget

* Mark trimmed caches for catch-up and prune discovery

* Bound sole oversized entries and reset discovery cursors

* Mark stripped entries incomplete

* Preserve full report and live fork protection during trim

* Compact protected fork parents over budget

* Enforce byte cap after underestimated encodes

* Exclude lineage-only parents from fork protection

* Re-encode after forced prune and bound lookback state

* Use report window for catch-up and exclude lineage parents

* Compact parents required by kept trim survivors

* Prune orphaned discovery mappings under budget

* Preserve lookback work and align report bounds

* fix: lower cost-cache load cap and never persist a refusable artifact

The 1 GiB load cap still let a 256 MiB-1 GiB legacy artifact be decoded
in one shot, which is exactly the MALLOC_LARGE multi-GiB spike #2637
traced. Since save now bounds Codex artifacts to 256 MiB, anything
meaningfully above the budget is legacy and cheaper to rebuild bounded;
drop the cap to 320 MiB (budget + enforcement slack).

Also make save uphold the loader's contract: when budget enforcement
cannot shrink the payload below the load cap (unstrippable resume or
buffered state), remove the artifact instead of writing one that every
launch would decode just to refuse.

* Preserve recursive lookback roots and share ID capacity

* Keep legacy lookback roots in the scan queue

* Preserve complete report across repeated trims

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: changelog for triage merge batch (#2649, #2613, #2600, #2646, #2648, #2650, #2643, #2651)

* Use OpenRouter server remaining for key limit meter (#2612)

* Use OpenRouter server remaining for key limit meter

* Treat negative OpenRouter remaining as exhausted quota

* Align JS OpenRouter meter with Swift quota path

* Clamp JS OpenRouter server remaining to key limit

Match the Swift quota path's inclusive [0, keyLimit] clamp so a server
remaining above the configured limit renders 0% used instead of
suppressing the meter. Adds an above-limit Swift/JS parity fixture.

* Validate selected OpenRouter fallback quota value

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: changelog for OpenRouter key-limit fix (#2612)

* build: sync widget-extension Package.resolved to SweetCookieKit 0.5.2

The root package moved to SweetCookieKit 0.5.2 but the widget project's
pinned resolved file lagged at 0.5.1, breaking Scripts/package_app.sh
(xcodebuild runs with automatic resolution disabled). CI does not build
the widget xcodeproj, so this only surfaced in local packaging.

* feat: localize Plugins pane (#2663)

* feat: localize Plugins pane

* docs: note Plugins pane localizations

* refactor: migrate provider payloads onto declarative details model (#2658)

* refactor: migrate provider details batch one

* refactor: migrate provider details batch two

* refactor: migrate provider details batch three

* fix: preserve provider detail rendering parity

* fix: migrate Copilot credits into provider details

* fix: pin Poe details to UTC

Poe timestamps are UTC and the daily buckets already used it, but the
recent-activity labels and Today bucket used the local zone in both the
Swift and JS paths, flaking the golden on non-Pacific runners. Golden
proven invariant under TZ=UTC, America/Los_Angeles, Asia/Tokyo.

* fix: normalize OpenRouter reset detail

* test: pin Poe menu fixture to UTC

* fix: preserve OpenRouter key details

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: decompose provider settings snapshot into provider-owned sections (#2659)

* refactor: decompose provider settings snapshots

* fix: restore empty provider settings factory

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: namespace provider-specific config fields into provider folders (#2661)

* test: characterize provider config JSON bytes

* refactor: namespace provider config fields

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* test: deflake refresh coalescing cleanup (#2672)

* fix: bound widget-snapshot file I/O with timeout and circuit breaker (#2673)

On macOS 26, opening the app-group snapshot can wedge indefinitely behind container or TCC I/O. Run snapshot reads and writes on bounded detached threads, then stop further process-local container access after the first timeout.

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* fix: stop infinite Overview flicker from agent-session rescan feedback loop (#2652) (#2674)

Hovering between provider chart submenus on the Overview tab with Agent
Sessions and Cost Summary enabled could flicker forever. Every menu open
(including each hover-opened hosted chart submenu) kicked off an
agent-session rescan whose completion unconditionally invalidated all
menus and rebuilt the tracked parent in place. Overview parents cannot
smart-update, so the structural rebuild replaced the hovered row and
force-closed its submenu; the reopen triggered another rescan, closing
the loop.

Three bounded changes break it:
- AgentSessionsStore no longer publishes rescans that reproduce the
  current local/remote session content.
- menuWillOpen triggers the session rescan (and menu-open note) only for
  root menus, not hover-opened submenus.
- Session updates invalidate menus with deferred tracked-parent rebuild
  and stale-content preservation, matching the store-observation path.

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: provider-owned credential adapters (#2676)

* test: characterize provider credentials

* refactor: add provider credential adapters

* refactor: register provider cookie settings

* refactor: move credential policy to providers

* fix: qualify static credential adapter

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: cut over Crof and Venice plugins (#2677)

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* feat: classified errors, deadlines, and overrides for JS plugins; cut over four more providers (#2678)

* feat: classify plugin fetch failures

* feat: add plugin request deadlines

* feat: support plugin request overrides

* refactor: cut over OpenRouter plugin

* feat: map plugin data confidence

* refactor: cut over ClawRouter plugin

* refactor: cut over Deepgram plugin

* refactor: cut over sub2api plugin

* style: satisfy plugin cutover lint

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* test: accept GMT/UTC alias in sub2api timezone golden (#2680)

* test: accept GMT/UTC alias in sub2api timezone golden

Foundation reports GMT where JS Intl reports UTC on UTC-configured
machines; the plugin sends the real current zone via Intl. Proven under
TZ=UTC, America/Los_Angeles, Asia/Tokyo. Unbreaks main CI after the
#2678 admin-merge left this red on UTC runners.

* style: satisfy redundant-type lint in sub2api golden

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: derive the remaining per-provider bookkeeping from descriptors (#2682)

* refactor: derive widget colors from provider branding

* refactor: generate provider manifests from one registry

* refactor: colocate provider cookie import priorities

* refactor: move share plan labels into descriptors

* refactor: move debug defaults into provider descriptors

* refactor: derive debug pane provider curation

* refactor: derive provider presentation capabilities

* refactor: unify standard provider config bindings

* style: remove stale lint suppression

* fix: use grep instead of rg in manifest regeneration script

CI lint runners do not have ripgrep; grep -rlE --include is portable and
matches the same files.

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: unify CLI onto descriptor-owned provider capabilities (#2685)

* test: characterize CLI provider policy output

* test: exempt CLI JSON golden from body limit

* test: scope CLI golden lint exemption

* refactor: derive CLI rendering from provider descriptors

* refactor: derive CLI source availability from descriptors

* refactor: finish descriptor-owned CLI credentials

* refactor: derive plan history policy from descriptors

* refactor: document CLI provider-specific boundaries

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: derive remaining provider registries and correct authoring docs (#2686)

* refactor: derive config validation capabilities

* refactor: derive provider log categories

* refactor: derive menu metric capabilities

* build: derive provider manifest order

* fix: preserve provider bootstrap order

* test: derive credential gatekeeper coverage

* docs: document provider registration truth

---------

Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>

* refactor: move usage presentation policy into provider descriptors (#2688)

* test: pin provider presentation policy

* refactor: move usage window policy into descriptors

* refactor: move usage menu policy into descriptors

---------

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* refactor: derive or justify every remaining provider special case (#2691)

* refactor: derive settings provider special cases

* refactor: justify usage store provider state

* refactor: derive controller provider policy

* refactor: derive cli provider capabilities

* refactor: justify scanner provider ownership

* test: gate provider special case clusters

* build: refresh codex parser hash

* style: satisfy special case gate lint

* style: accept cli cards command size

---------

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* test: harden the provider special-case gatekeeper and derive its findings (#2693)

* test: harden provider architecture gatekeeper

* refactor: derive provider presentation policies

* test: pin provider dispatch constructs

* test: document gatekeeper catalog lint scope

---------

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* refactor: close remaining provider-architecture gaps and declare gatekeeper scope (#2694)

* refactor: close provider architecture gaps

* refactor: triage loose provider references

* test: harden provider architecture gatekeeper

* docs: declare gatekeeper threat model

---------

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* refactor: close provider architecture gaps (#2698)

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* test: close provider gatekeeper gaps (#2699)

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* fix: harden provider gatekeeper lexer (#2700)

Co-authored-by: Peter Steinberger <steipete@clawstudio.local>

* fix: scope provider gatekeeper suppressions (#2702)

* test: broaden provider architecture gatekeeper (#2704)

* test: normalize inline block comments in gatekeeper scanning (#2705)

Blank single-line block comments with index-preserving spaces so
punctuation adjacency stays visible to position heuristics; document
nested-suppressed-call attribution and multi-line block comments as
parser-requiring out-of-scope patterns.

* fix: retain flicker-probe sessions so release builds actually record (#2709)

ProbeSession(...).begin() ran as an unretained temporary whose only other
reference was the driving timer's [weak self], so release-mode builds
deallocated the session at creation (compiler: "weak reference will always
be nil") and the probe recorded nothing. Sessions are now strongly owned in
MenuSwitchFlickerProbe.activeSessions until finish() unregisters them, and
ProbeSession gained a Configuration seam (timings + menu opener) so a test
can run a full session against a synthetic merged menu and prove it records
switch activity and frame samples.

* feat: nest claude-swap accounts in dashboard snapshot (#2713)

* Fix Codex manual reset refresh (#2710)

* Fix Antigravity agy cold-start quota readiness wait (#2665)

* fix(serve): bound the whole request head, not just each read (#2684)

* fix(serve): bound the whole request head, not just each read

`readRequest` applied `requestReadTimeoutMilliseconds` per `recv` with no overall
limit, so a client trickling one byte just inside that window never timed out and
held its connection — and the cooperative-executor thread serving it — for as long
as it kept sending. The Host allowlist and bearer-token check both run after the
head is read, and over-cap connections are closed rather than queued, so a few such
clients denied service to well-behaved ones entirely pre-auth.

Track a monotonic start time and cap each wait at the remaining budget, failing the
request once the overall deadline passes. A client that merely goes silent was
already handled by the per-read timeout; this covers the one that keeps sending.

Verified red→green on Linux: before, a legitimate client never got a slot within a
25s budget; after, it is served at 10.26s, matching the deadline.

* fix(test): gate the serve deadline suite to Linux

`TestsLinux` is declared unconditionally in Package.swift, so this file also
compiles on macOS, where the raw socket calls (`SOCK_STREAM.rawValue`, Glibc-only
imports) do not build and broke both macOS test shards.

Wrap the file in `#if canImport(Glibc) || canImport(Musl)`, matching
AntigravityProcessLauncherLinuxTests and the other syscall suites here.

* test(serve): inject a short deadline so the suite cannot stall others

The deadline test held executor threads for the full 10s production budget, which
starved unrelated suites: CLIHooksWatchSleepLinuxTests asserts a stop signal is
honored within 2s and instead measured 10.31s on CI. `.serialized` only orders
cases within a suite, so it does not prevent that.

Make the budget injectable on CLILocalHTTPServer, defaulting to the production
value, and have the test pass 1500ms. Full suite drops from ~10.5s to ~2.0s.

Verified the test still fails when the deadline logic is disabled (12.37s
starvation), so the shorter budget did not weaken it, and ran the full suite 8x
with no flake.

* docs: add CodexBar Meter to Linux desktop integrations (#2696)

* feat: add KRW to the preferred currency picker (#2669)

KRW was the one major Asian currency missing from the picker added in
#2490, so won-billed users converted USD estimates by hand.

No new exchange-rate logic is needed: fetchLatestRatesIfNeeded already
merges every rate in the open.er-api.com payload, which carries KRW.
Listing the code in supportedCurrencies is what opens the
requiresLiveRates gate, so selecting KRW triggers the same live-fetch and
24h-cache path as the existing currencies. The hardcoded fallback rate is
only read before the first successful fetch.

KRW is zero-decimal and currencyString pins en_US, so ICU resolves the
fraction digits and won renders without a fractional separator on any
host.

Refs #2449

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Align cost cache save overshoot with documented load cap (#2703)

* Align cost cache save overshoot with documented load cap

* Keep preceding codex cache producer key compatible

* Keep current main codex cache producer key compatible

* fix: accept all shipped codex producer keys back to #2632

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix(commandcode): Add support for individual-goat plan ($70/mo credits) (#2706)

* fix(commandcode): Add support for individual-goat plan (/mo credits)

* fix(commandcode): Support new commandcode_prod_.session_token cookie name

* fix(packaging): Restore widget extension packaging in package_app.sh

* fix(commandcode): preserve legacy bare-token cookie fallback

Keep __Secure-better-auth.session_token as the bare-token default until a
renamed production cookie is proven live; accept the new
commandcode_prod_ cookie names additively with regression coverage for
both name families.

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: changelog for review round (#2710, #2665, #2684, #2706, #2703, #2669)

* feat(cli): add built-in web dashboard to codexbar serve (#2715)

GET / now serves a self-contained HTML page that polls
/dashboard/v1/snapshot and renders provider cards with usage windows,
credits, and cost. The static UI stays unauthenticated (it carries no
account data); tokens are entered in-browser, kept in localStorage, and
sent as a bearer header. No visibility gating on polling so embedded
panes that report document.hidden permanently still refresh.

* Document Antigravity data sources when the app is closed (#2666)

* Document Antigravity sources when the app is closed

* docs: keep Antigravity experimental tag and reflect cold-start readiness wait (#2665)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix(cli): deliver late serve results instead of discarding them (#2717)

When a serve operation outlived its request deadline, the coordinator
marked the slot timed out and threw away the eventually-produced value:
it never reached the accept commit (response cache) and the queued
same-config successor restarted the source from zero. On machines where
the dashboard snapshot build outlasts --request-timeout, GET
/dashboard/v1/snapshot 504ed forever while each retry burned another
full cost-history scan.

Timed-out sources now still commit through accept and hand the fresh
result to a same-fingerprint successor without rerunning the source;
changed-config successors promote as before. The web UI shows a
friendlier 504 message noting it retries automatically.

* feat(cli): add --identity redacted|full to the dashboard snapshot command (#2716)

The one-shot `codexbar dashboard` always redacted account identity even
though the builder supports full mode. Personal dashboards on trusted
private networks (e.g. a tailnet portal) want real account emails.
The serve HTTP transport is unchanged and stays always-redacted;
--identity none is rejected.

* feat(cli): add --output <path> to the dashboard snapshot command (#2719)

Static-webroot publishers currently shell-wrap the one-shot command
(codexbar dashboard > tmp && install tmp target) to avoid readers seeing
a partial document. --output writes the snapshot atomically instead:
staged temp file in the destination directory, fsync, rename(2) over the
target, 0644. Stdout stays untouched when the flag is absent and silent
on success when writing to a file. Empty paths are an args error and a
missing parent directory fails with a clear message; directories are
deliberately not created.

* feat(serve): render claude-swap accounts in the web dashboard; add --identity full (#2720)

The built-in web dashboard ignored the dashboard-v1 accounts[] array, so
multi-account Claude setups showed a single ambient row, and the serve
transport's always-redacted identity left the email unknown. Per-account
sections now replace the ambient windows when accounts are present (label
fallback when identity is redacted), accountsError surfaces on the card,
and a serve-level --identity redacted|full startup flag reuses the
dashboard command's decoder so trusted private deployments can show real
emails. Default stays redacted.

* docs: retitle unreleased to 0.48.0 and order changelog by user impact

* feat(serve): spend charts, provider brand icons, grouped card layout for the web dashboard (#2722)

The web dashboard showed only aggregate cost numbers while the app has
graphs, used a plain top-stripe accent, and nested claude-swap accounts
as rows inside one card. It now fetches /cost daily buckets and renders
an inline SVG spend chart per provider (failures never block the
snapshot render), serves the app's provider brand SVGs from an embedded
/icons/ route (names validated against the embedded set; immutable
cache), and lays out multi-account providers as one card per account
under a titled group with status pills, replacing the border stripe
with an icon-led header.

* fix(serve): hide the web dashboard status chip when no provider status exists (#2723)

The serve snapshot collector never fetches provider status pages, so
status is always nil and every card rendered a gray 'unknown' chip.
Render the chip only when the snapshot actually carries status data.

* chore: bump version to 0.48.0 (112) and date the changelog

* style: fix 69 SwiftLint violations from serve web-dashboard merges

The serve dashboard PRs (#2715-#2723) merged during the GitHub Actions
outage without CI lint validation. Mechanical, behavior-preserving fixes:

- CLIServeProviderIcons.swift: generator now emits scoped
  swiftlint:disable/enable line_length around the base64 data (icon
  payload is byte-identical after regeneration)
- CLIServeWebUI.swift: moved the html template constant to
  CLIServeWebUI+HTML.swift (enum body 1093 -> under 800 lines); the
  string literal content is verbatim-identical
- Data->String conversions switched to failable String(bytes:encoding:)
  with fallbacks, matching repo convention
- Long @Option help strings wrapped via adjacent + concatenation

* chore: publish 0.48.0 appcast entry

* chore: open 0.48.1 unreleased changelog section

* fix: make usage labels follow bar fill (#2744)

* feat(cli): paint the serve dashboard instantly and stream data in (#2746)

Three layers, one goal: never make the browser wait on a snapshot build
(measured 46.5s cold vs 22ms warm on the portal host).

- Stale-while-revalidate: expired cache entries within staleTTL answer
  immediately from the last-good response while a coalesced background
  rebuild commits fresh data. Applies to /usage, /cost, and the
  dashboard snapshot; failure-fallback rules are unchanged.
- Snapshot query params (schema stays v1): ?provider=<id> builds and
  caches one provider row independently; ?detail=shell returns
  config-only rows with no provider fetches or cost scans.
- Web UI: renders the last snapshot from localStorage instantly on
  revisit, paints skeleton cards from the shell on cold start, fills
  each card as its provider resolves, then hands off to normal polling.

* fix: pull 0.48.0 from the appcast — packaged app crashes at launch loading the CodexBarCore resource bundle (#2738, #2730)

* fix: resolve CodexBarCore resource bundle safely in packaged apps

0.48.0 crashed at launch on every machine without the build worktree:
the plugin runtime's Bundle.module accessor only probes the app root and
the compiled-in build path, but the packaged app ships the bundle in
Contents/Resources. Route all CodexBarCore resource loads through a safe
resolver (mirroring the existing localization/brand-icon pattern), and
add a gate test forbidding raw Bundle.module in CodexBarCore.

Fixes #2738. Fixes #2730.

* chore: bump version to 0.48.1 (113) and date the changelog

* feat(cli): default dashboard identity to full emails (#2748)

Redaction was the dashboard-v1 default at every layer; the --identity
flag already existed on both the one-shot command and serve. Flip the
default to full identity and keep --identity redacted as the opt-in
privacy setting for snapshots that cross untrusted networks. The web
UI stops re-redacting rows before persisting them to localStorage so
cached paints match what the server serves in either mode.

* docs: update appcast for 0.48.1

* Decode Codex monthly credit limit from spend_control.individual_limit (#2737)

* Decode Codex monthly credit limit from spend_control

* Keep spend_control below root and rate_limit precedence

* Centralize credit-limit precedence in resolvedIndividualLimit

* docs: move Codex spend_control credit-limit entry to 0.48.2 (#2737)

* fix(cli): keep claude-swap account emails when the usage fetch fails (#2752)

An account's name was derived only from its usage snapshot, which the
projection returns as nil for any status other than ok. Accounts with
expired or missing credentials therefore lost their identity and fell
back to the hardcoded "Account N" slot label, even though claude-swap
reports an email for every slot and the menu bar app shows it.

Resolve the email from the projection's displayLabel when the snapshot
has none, and feed that single value through the existing identity-mode
treatment for both label and identity so redacted mode stays honest.

* fix(zai): parse CREDIT_LIMIT quota entries; restore 5-hour primary reset (#2751)

* fix(zai): parse CREDIT_LIMIT quota entries from credit-based plans

z.ai's GLM Coding Plans (lite/standard/pro) now return CREDIT_LIMIT
instead of TOKENS_LIMIT from the quota API. Both the Swift parser and
the bundled zai.js plugin rejected the unknown type, leaving accounts
stuck at 100% remaining / 0% used and letting the reset display fall
back to a non-5-hour limit.

Treat CREDIT_LIMIT like TOKENS_LIMIT for window selection, window
minutes, and the 5-hour reset description; label credit plans as
"Credit quota" / "Session credit quota"; accept "level" as a plan
name in the plugin for Swift parity. Adds a CREDIT_LIMIT Swift/JS
parity fixture.

Squashed from PR #2725.

Fixes #2724

* test(zai): lock CREDIT_LIMIT parsing and 5-hour reset selection (#2724, #2712)

Parser-level regression tests built from the exact payload reported in
#2724: the 5-hour credit window becomes the primary lane (300-minute
window, 3.55% used, "5-hour" reset description) and the weekly credit
window the secondary, with the primary reset timestamp taken from the
5-hour entry rather than a fallback limit's longer schedule (#2712's
reported symptom). Adds the changelog entry crediting @stuible.

---------

Co-authored-by: Josh Stuible <joshstuible@gmail.com>

* fix(claude): render the menu-bar indicator from the active claude-swap account (#2750)

* fix(claude): render the menu-bar indicator from the active claude-swap account

When the claude-swap adapter owns Claude account presentation (2+
accounts, or 1 with the single-account opt-in), the menu renders adapter
account cards while every menu-bar icon path still read the ambient
Claude snapshot. With an ambient probe that yields no usable rate
windows, the bar drew empty even though the adapter had fresh usage for
the active account.

Route the icon render, layout/reset scheduling, unified-icon provider
pick, highest-usage ranking, icon observation signature, and switcher
mini-bars through a single menu-bar presentation-snapshot selector that
prefers the active claude-swap account snapshot and falls back to the
ambient snapshot when the adapter is disabled, below its presentation
threshold, or the active account has no usable usage.

Fixes #2731

* test: reconcile provider gatekeeper anchors for cswap menu-bar fix

* build: add packaged-app launch smoke check to the packaging pipeline (#2755)

0.48.0 crashed at launch on every machine without the build checkout
because SwiftPM's Bundle.module accessor only probes the app root and
the compile-time build path; local testing passed only because the
build machine still had the checkout at that path. Release packaging
now copies the packaged app to a temp dir and runs it with sandbox-exec
denying every file read under the checkout: a deterministic
CODEXBAR_RESOURCE_SMOKE=1 probe forces the resource loads that trapped
in 0.48.0 (they are lazy, so a liveness check alone cannot reach them),
then a 6s direct-launch liveness check guards other startup fatals.
Direct binary spawn skips LaunchServices and cleanup kills only the
spawned PID, so the check cannot fight a running production CodexBar.
Headless runs hard-fail only on the resource-bundle trap signature.

Pipeline guard suggested by the reporter of #2738.

* feat: portable QuickJS plugin engine; run JS providers on Linux (#2753)

* build: vendor pinned quickjs-ng engine

* feat: add portable QuickJS plugin engine

* refactor: remove Linux provider twins

* fix: use canImport C-library chain in CLIPluginsCommand for musl

* Fix CLI resource bundle resolution and packaging (#2757)

* fix: ship and resolve the CodexBarCore resource bundle for CLI contexts

Fixes #2756

* fix: support Linux CLI resource layouts

* Add codexbar-cosmic-applet to Linux desktop integrations (#2734)

* Handle Codex reset credits omitted after redemption (#2728)

* Handle consumed Codex reset credits omitted by provider

* Exclude expired Codex reset credits from consumption detection

* docs: changelog for omitted reset credits fix (#2728)

* Kimi: hide Code 7-day row when it duplicates the weekly lane (#2741)

* Kimi: hide Code 7-day row when it duplicates the weekly lane

GetUsages (FEATURE_CODING detail) and GetSubscriptionStats (ratelimitCode7d) report the same 7-day Code quota through two endpoints; live data shows identical usage and reset times. Hide the extra row when both lanes agree (within 1pt and 5 minutes), keep it when they diverge or the weekly detail is unreliable.

* Require positive evidence before hiding Kimi Code 7-day row

* Align Kimi lanes with official usage naming

* fix: align menu bar reset countdown with menu (#2735)

* docs: changelog for Kimi lane and countdown fixes (#2741, #2735)

* feat: cut over Synthetic, Poe, xAI, and z.ai to JavaScript on all platforms (#2758)

* feat(synthetic): cut over provider to JavaScript

* feat(poe): cut over provider to JavaScript

* feat(xai): cut over provider to JavaScript

* feat(zai): cut over provider to JavaScript

* style(zai): use predicate count

* test: give the hung-script watchdog assertion CI headroom

The 0.15s interrupt must fire promptly, not wait out the hang; the 1s
elapsed bound flaked at 1.66s on a loaded ARM64 runner. 5s still proves
prompt termination against an unbounded loop.

* test: give hooks-watch interrupt assertion CI headroom

The 0.3s stop signal must beat the 10s interval; the 2s elapsed bound
flaked at 2.02s on a loaded x64 runner. 5s still proves prompt
interruption.

* docs: permit opt-in Claude statusLine JSON as an explicit data source (#2733)

* Menu: let metric meta and reset rows wrap instead of truncating (#2742)

* Menu: let metric meta and reset rows wrap instead of truncating

Compact usage detail rows (#2620) put all pace detail on one line and moved reset times into the title row; longer locales (e.g. Chinese) truncate tail content. Allow the meta line and reset label to wrap to two lines so all information stays visible while keeping the compact layout.

* Menu: track wrapping metric text in height-cache fingerprint

* fix: track reset title width in height cache

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: changelog for wrapping metric rows (#2742)

* feat: add SQLite CostUsageStore foundation (refs #2760) (#2761)

* feat: cut Codex cost persistence over to SQLite, delete the JSON artifact (refs #2760) (#2762)

* feat: cut Codex cost persistence over to SQLite

* docs: document SQLite cost history storage

* test: reconcile provider gatekeeper for the SQLite cutover

* fix: share the cost store serial executor

* fix: keep the cost-usage database on transient SQLite failures, only rebuild on corruption (refs #2760) (#2765)

withDatabase treated every error as corruption and deleted the entire
database. That destroyed user history on lock contention (app and CLI
both open writable connections), disk-full, and even a plain NOT NULL
constraint violation from appending rows for an unregistered path --
which then failed again on the fresh database and nuked it a second time.

Classify SQLite primary result codes: transient/environment/data-shape
errors (BUSY, LOCKED, FULL, NOMEM, IOERR, CONSTRAINT, READONLY, ...) now
return the fallback and keep the file; corruption and schema drift
(CORRUPT, NOTADB, ERROR, invalid data, incompatible schema) still
rebuild. Rebuilds and preserved failures are now logged via the
token-cost category so field rebuilds are observable, and a failed
transaction that could not roll back drops the connection instead of
leaving it wedged.

Adds a failure-injection suite: constraint orphans, connection reuse
after a rolled-back transaction, mid-file structural corruption, deleted
-wal / stale -shm sidecars, zero-byte databases, the database path
occupied by a directory, and a future schema version on disk.

* fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760) (#2767)

* fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760)

Semantics-parity audit of the CostUsageCache -> CostUsageStore migration
found four behaviors the cutover dropped:

- Retention pruned only the typed discovery columns while the scanner
  round-trips discovery through the opaque payload, so deleted files
  resurfaced on the next load; prune now updates both in lockstep and
  resets cursors/isComplete like the old cache did.
- The row budget deleted the oldest files regardless of window; the old
  entry budget never sacrificed in-window or recently active files (the
  byte budget remains the only authority that may).
- Fork-parent protection ignored the lineage-only dependency key and a
  stale parent survived when its only referencing child was pruned in
  the same pass; candidates now honor the dependency key and iterate to
  a fixpoint.
- Out-of-window files with an in-window mtime (active sessions with
  unscanned rows) were deletable; they are protected again.

saveCodexCache budgets are injectable for tests, restoring pins for
previous-report preservation across trims and the non-Gregorian
catch-up report (#2703), plus strip/compaction coverage.

* docs: changelog for the SQLite retention semantics parity fixes (refs #2760)

* perf: precompute per-path baseline counts in saveCodexCache instead of rescanning all rows per file (refs #2760) (#2770)

* test: make cost-usage debounce and token-hydration tests order-independent (#2777)

- Prevent a background models.dev pricing refresh from flipping codexPricingKey mid-test.
- Replace the fixed 1s poll that raced the process-global CostUsageScanExecutor.

* test: add StoreStress harness and contention regression tests (#2766)

* fix: stabilize OpenRouter plugin parity (#2779)

* Fix Codex cost pricing race (#2776)

* fix: price persisted usage at report read time

* fix: reprice project usage snapshots at read time

* test: add corpus-scale proof gates for the SQLite cost store (refs #2760) (#2763)

* test: add corpus-scale proof gates for the SQLite cost store (refs #2760)

* test: freeze scale-proof reference date and assert exact retention count

* test: bound persisted size of legacy payload

* fix: wrap the cost-store save cycle in one transaction so a crash mid-save is all-or-nothing (refs #2760) (#2771)

* fix: wrap the cost-store save cycle in one transaction so a crash mid-save is all-or-nothing (refs #2760)

* fix: inline save-transaction control so the operation closure stays actor-isolated

* fix: replace the save-transaction closure with begin/end calls to satisfy older Swift 6 region analysis

* test: widen slow-refresh sleeps in CLIServeRouterTests so the request deadline always wins the race on loaded CI runners

* Add CodexBar Plasma integration (#2768)

Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>

* Add Fireworks provider (30-day billing spend) (#2687)

* Add Fireworks provider showing 30-day billing spend

* Fix Fireworks URL/auth interpolation and test assertions

* Validate Fireworks account slug; add malformed-slug test

* fix: keep recently modified files out of cost-store window pruning (refs #2760) (#2764)

* fix: keep recently modified files out of cost-store window pruning (refs #2760)

* fix: use inclusive calendar-day bounds for retention mtime recency

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: changelog for Fireworks provider and calendar-day pruning fix (#2687,…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. P2 Normal priority bug or improvement with limited blast radius. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] codexbar serve has no overall request deadline — trickling clients can hold every connection slot pre-auth

2 participants