Repository navigation
acpmux: forward a Claude Code chat's plan usage limits to clients - #24293
Conversation
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Gallery before/after for
States
Pick other states with a web and size checks: green |
|
Bead |
|
Found 9 test failures on Blacksmith runners: Failures
|
|
Review (codex xhigh, correctness first): SHIP at 4caa299 No blocking correctness, compile, wiring, test-registration, race, or schema findings. (Independent Codex reviewer at xhigh effort on big-red, read-only, over |
|
Orphaned: no activity for 14 h, and no lane holds a lease on it. The orphan sweep could not merge |
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (codex xhigh, correctness first): SHIP at e7d55c5 No blocking correctness, compile, clippy, race, or test-isolation findings. (Independent Codex reviewer at xhigh effort on big-red, read-only, over At e7d55c5:
This PR includes #24223's commits until #24223 merges. Then I'll merge feat-cmux-next in so the diff is only this change, and re-review at that head before ready-to-land. |
|
|
|
|
e7d55c5 to
3f4e677
Compare
|
|
… drops them (red) fake_claude sends rate_limit_event lines (rate-limits-probe, FAKE_CLAUDE_LIMITS=1, and a late one from a replaced process). The chat's detail must carry usageLimits, newest per window, and lose them on a harness failover; a replaced process's late limits are neither logged nor applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code's stream-json rate_limit_event lines become the session
detail's usageLimits and a session_info_update with
_meta.acpmux.usageLimits: {windows: [{id, usedPercent 0-100, resetsAt?}]},
newest per window. five_hour is session, seven_day weekly, seven_day_opus
weekly_opus, seven_day_sonnet weekly_sonnet; overage windows are left out.
A failover clears them (the old account's), and a retired agent's late
limits are dropped by the same guard as its /context answer
(agent::process_state, in tap_live and the hub's generation check). A
change is saved with the meta, as a breakdown is.
Rebased onto feat-cmux-next: #24223/#24272 (context breakdown, MCP
reconnect) already landed, so this carries only the usage limits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3f4e677 to
f4d4f56
Compare
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
Leo asked for a fuller Context card in the session sidebar: "i feel like having a teensy bit more of the card type thing with fuller info in the session sidebar would be nice? like just make our context circle have a bit more stuff". A sibling PR draws the account's usage limits in that card. This PR gives it the data. acpmux now forwards a Claude Code chat's plan usage limits (the 5-hour session and the weekly windows, with their reset times) to clients.
Leo-ask: ask-af4ecbf18f
Claude Code (stream-json, 2.1.30x) writes
{"type": "rate_limit_event", "rate_limit_info": {...}}lines. acpmux turns each one into:session_info_updatewith_meta.acpmux.usageLimits, andusageLimits(next tousageandcontextBreakdown), saved in the session meta the same waycontextBreakdownis.Both have this shape:
{ "windows": [{ "id": "session" | "weekly" | "weekly_opus" | "weekly_sonnet", "usedPercent": 0-100, "resetsAt"?: unix seconds }] }.five_hourmaps tosession,seven_daytoweekly,seven_day_opustoweekly_opusandseven_day_sonnettoweekly_sonnet. The overage windows (overage,seven_day_overage_included) are extra paid usage, not a plan limit, so they are left out.utilizationis a 0-1 fraction. Claude Code 2.1.307's own schema says so: "utilization is the fraction of the window used (usually 0-1 ...; values above 1 occur when usage legitimately runs past a window's cap)". acpmux multiplies it by 100, rounds to two decimals and caps it at 100.rateLimitType+utilization+resetsAt) and can also carryunifiedWindows. When both name the same window,unifiedWindowswins.usageLimits, because they belonged to the old account. It is handled like acpmux: Claude Code's /context breakdown and MCP reconnect #24223'scontextBreakdown: a retired agent's lateusageLimitsupdate is dropped before the tap logs it (agent::process_state, checked by base's retire fenceChildAgent::tap_liveand the hub's generation check under the child lock).plan. The stream does not name a plan reliably: neithersystem/initnorrate_limit_eventcarries one. Soplanis never set, though the merge keeps one if a future source adds it.account/rateLimits/updatedas_meta.codexAppServer.rateLimits(primary/secondarywindows withusedPercent,windowDurationMins,resetsAt, plusplanType). It can be mapped. But the adapter merges sparse updates at the top level regardless oflimitId, so a snapshot for another quota bucket can overwriteprimary. That makes the mapping unreliable until the adapter keys windows by limit.The protocol doc
docs/acpmux-schema.json(SessionDetail) now describesusageLimits. No webview change: the UI is the sibling's job, and no generated type changes.#24223 (context breakdown) and #24272 (MCP reconnect) have landed, so this branch now carries only the usage limits (+341/-18).
Testing
claude_stdio/usage_limits_tests.rs:unifiedWindowsin one event, with overage left outtests/hub_integration/usage_limits.rs:fake_claude.pyrate-limits-probesends tworate_limit_eventlines, and the test checks_acpmux/infousageLimitsand the twosession_info_updates.claude_failover.rs: the limits are there before a failover and gone after it. A replaced process's laterate_limit_eventis neither applied nor logged.Observed / Not run
cmux-ci compile-check --apppassed.prepush-gate.pyreports no new violations.Changelog
Added: Claude Code chats report the account's session and weekly usage limits, with reset times, to the agent pane
Proof
No capture: acpmux data only. The sibling UI PR shows the card.
Adopted and rebased (2026-10-25)
The stale-pr-reconciler, word for word: "These PRs in your area have no live owner (Leo, 2026-10-25: "these should never have been this stale for this long"). Adopt each:
pr-owner claim N(in that repo), rebase onto its base, an independent review at the new head, then land it".tap_live, and a generation check under the child lock in the hub). I kept base's fence and widened both checks fromcontextBreakdowntoagent::process_state, which coverscontextBreakdownorusageLimits.breakdown_changed |= m.usage_limits != merged). An identical update writes nothing. Claude Code sendsrate_limit_eventonly when a window's rounded percent or reset moves.Tests on the rebased branch (fast-checks on Blacksmith)
usage_limits::a_claude_chat_keeps_its_accounts_usage_limits,claude_failover::a_failover_drops_the_last_harnesss_context_breakdownanda_replaced_harnesss_late_context_breakdown_is_not_keptfail.--lib usage_limits4 pass;hub_integrationusage_limits/claude_failover/fork_through/context_window 96 pass. In the fullcargo test -p acpmux, everything passes exceptdaemon_chat_open::a_long_chat_opens_on_its_newest_page...("opening the chat read 5597930 bytes (budget 4000000)"), which fails the same way on base 7bd097d. clippy-D warningsand fmt are clean at 2ccfca3.Script tests(fails on base),Package dependency lists sorted(this PR touches no Swift package), and GitHub API rate limits or 403s in fast-gates status, merge-tree publish, same-tree cmux-tui and the Windows R2 attestation.No timers or observers added.
Agent session: big-red
🤖 Generated with Claude Code