Skip to content

acpmux: forward a Claude Code chat's plan usage limits to clients - #24293

Merged
teamleaderleo merged 3 commits into
feat-cmux-nextfrom
sp-usage-limits
Oct 26, 2026
Merged

teamleaderleo merged 3 commits into
feat-cmux-nextfrom
sp-usage-limits

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

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:

  • a session_info_update with _meta.acpmux.usageLimits, and
  • the session detail field usageLimits (next to usage and contextBreakdown), saved in the session meta the same way contextBreakdown is.

Both have this shape: { "windows": [{ "id": "session" | "weekly" | "weekly_opus" | "weekly_sonnet", "usedPercent": 0-100, "resetsAt"?: unix seconds }] }.

  • Window names. five_hour maps to session, seven_day to weekly, seven_day_opus to weekly_opus and seven_day_sonnet to weekly_sonnet. The overage windows (overage, seven_day_overage_included) are extra paid usage, not a plan limit, so they are left out.
  • Scale. utilization is 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.
  • Which windows an event carries. An event can name its limiting window (rateLimitType + utilization + resetsAt) and can also carry unifiedWindows. When both name the same window, unifiedWindows wins.
  • Newest value per window. A later event for one window updates only that window. The adapter keeps what this Claude Code process has reported so far. The hub merges each update into the saved value by window id, so a respawned Claude Code does not erase the windows it has not reported yet. A fork copies its parent's limits, because they belong to the account.
  • Which session gets it. The update always goes to the chat's own session, even when a subagent's stream reported the limits.
  • Failover. A harness failover clears the chat's usageLimits, because they belonged to the old account. It is handled like acpmux: Claude Code's /context breakdown and MCP reconnect #24223's contextBreakdown: a retired agent's late usageLimits update is dropped before the tap logs it (agent::process_state, checked by base's retire fence ChildAgent::tap_live and the hub's generation check under the child lock).
  • No plan. The stream does not name a plan reliably: neither system/init nor rate_limit_event carries one. So plan is never set, though the merge keeps one if a future source adds it.
  • Codex: follow-up, not mapped here. cmux-codex-acp forwards Codex's account/rateLimits/updated as _meta.codexAppServer.rateLimits (primary/secondary windows with usedPercent, windowDurationMins, resetsAt, plus planType). It can be mapped. But the adapter merges sparse updates at the top level regardless of limitId, so a snapshot for another quota bucket can overwrite primary. That makes the mapping unreliable until the adapter keys windows by limit.

The protocol doc docs/acpmux-schema.json (SessionDetail) now describes usageLimits. 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

  • Unit tests in claude_stdio/usage_limits_tests.rs:
    • fraction to percent conversion, including a value above 1 capped at 100
    • the limiting window and unifiedWindows in one event, with overage left out
    • a later event updating only its own window
    • malformed and overage-only events sending nothing
    • the hub's merge by window
  • Hub integration test tests/hub_integration/usage_limits.rs: fake_claude.py rate-limits-probe sends two rate_limit_event lines, and the test checks _acpmux/info usageLimits and the two session_info_updates.
  • claude_failover.rs: the limits are there before a failover and gone after it. A replaced process's late rate_limit_event is neither applied nor logged.

Observed / Not run

  • Observed at e7d55c5:
    • cmux-ci compile-check --app passed.
    • The fleet Rust tests (4 shards) ran. All 9 usage-limits and failover tests pass. The red tests that also fail alone fail the same way in an unrelated lane's run (base reds).
    • PR CI's clippy and rustfmt for the changed cmux-tui packages passed. rustfmt also passes locally, and prepush-gate.py reports no new violations.
  • Not run: a live Claude Code session on a subscription account. The line shape comes from the Claude Code 2.1.307 bundle's own builder and zod schema, not from a captured stream.

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

  • Re-applied only this PR's own net diff onto feat-cmux-next (it was 3,086 commits behind and carried acpmux: Claude Code's /context breakdown and MCP reconnect #24223). Base had hardened the retired-agent fence for /context answers (a retire lock in tap_live, and a generation check under the child lock in the hub). I kept base's fence and widened both checks from contextBreakdown to agent::process_state, which covers contextBreakdown or usageLimits.
  • A change to the merged limits now marks the meta for saving, as a breakdown change does (breakdown_changed |= m.usage_limits != merged). An identical update writes nothing. Claude Code sends rate_limit_event only when a window's rounded percent or reset moves.
  • Review: Codex gpt-6-astra xhigh, SHIP at 2ccfca3 with no findings ("The reapplication preserves the base's retirement fencing ... Late retired-agent usage updates are excluded without changing context-breakdown behavior").

Tests on the rebased branch (fast-checks on Blacksmith)

  • Red at the test commit 8a6497e: usage_limits::a_claude_chat_keeps_its_accounts_usage_limits, claude_failover::a_failover_drops_the_last_harnesss_context_breakdown and a_replaced_harnesss_late_context_breakdown_is_not_kept fail.
  • Green at f4d4f56: --lib usage_limits 4 pass; hub_integration usage_limits/claude_failover/fork_through/context_window 96 pass. In the full cargo test -p acpmux, everything passes except daemon_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 warnings and fmt are clean at 2ccfca3.
  • PR CI reds that aren't from this PR: 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

@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 22, 2026
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 22, 2026 07:49 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review f4d4f56: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr24293 -f ref=sp-usage-limits -f platform=macos -f backend_mode=local

Gallery before/after for e7d55c5: 16 states rendered, 9 images differ, 1616 more touched states not rendered. Images are in the run's artifact, not on this public PR.

gh run download 39894737213 -R manaflow-ai/cmux -n gallery-advisory-24293-e7d55c592729f89823da906616df43edc020eb8b (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.addressed-message/relayed Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.addressed-message/relayed Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/sent Apple System Colors changed 0.017% imports model.ts (1 hops)
agent-pane.agent-message-row/sent Apple System Colors Light changed 0.0159% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-subagents Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-subagents Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-hover Apple System Colors changed 0.0139% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-hover Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-opened Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-opened Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-opened-long Apple System Colors Light changed 0.0001% imports model.ts (1 hops)
agent-pane.agent-message-row/sent-opened-long Apple System Colors changed 0% imports model.ts (1 hops)
agent-pane.agent-message-row/outcomes Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/outcomes Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/both-directions Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/both-directions Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-message-row/both-directions-opened Apple System Colors Light changed 0% imports model.ts (1 hops)
agent-pane.agent-message-row/both-directions-opened Apple System Colors changed 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/paused Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/paused Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/held-list Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/held-list Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/repeating Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/repeating Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/cap Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/cap Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/release-failed Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/release-failed Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/resumed Apple System Colors Light unchanged 0% imports model.ts (1 hops)
agent-pane.agent-pair-note/resumed Apple System Colors unchanged 0% imports model.ts (1 hops)
agent-pane.background-task-note/on-call Apple System Colors Light changed 0.0124% imports model.ts (1 hops)
agent-pane.background-task-note/on-call Apple System Colors changed 0.0119% imports model.ts (1 hops)

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

web and size checks: green

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-t5ork created for this PR by the cmux coordinator.

@github-actions github-actions Bot added risk:high Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Oct 22, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Found 9 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpbfokos9c/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpuacxrarl/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'/
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'
View Logs
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'/
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'
View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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 git diff 4caa299e2af3~1 4caa299e2af. cmux-ci compile-check --app passed at this head; the fleet Rust test, clippy and fmt runs are still queued.)

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Orphaned: no activity for 14 h, and no lane holds a lease on it. The orphan sweep could not merge feat-cmux-next in: conflicts in webviews/src/agent-session/acpmux/direct.ts. Listed for a lane to resolve.

@cursor

cursor Bot commented Oct 23, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@cursor

cursor Bot commented Oct 23, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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 git diff a2532878f32 e7d55c5927, which is this PR's own change on top of #24223's tip. The round at b7e872f was a BLOCK: a replaced process's update was logged before the hub dropped it, plus clippy nonminimal_bool. Both are fixed: agent::process_state now covers usageLimits in #24223's ChildAgent::retire / stale_context path.)

At e7d55c5:

  • cmux-ci compile-check --app passed.
  • Fleet Rust tests (cmux-tui-rust-check.sh test, 4 shards) ran. All 9 usage-limits and failover tests pass, including claude_failover::a_replaced_harnesss_late_context_breakdown_is_not_kept.
  • Remaining reds: the ones that also fail alone (remote_guard_codex_bridge, terminal_host_recovery idle_redrawing, cli::mcp upstream_tests, cmux-tui::setup, server_stop) fail the same way in an unrelated lane's fleet run, so they are base reds. The rest passed when rerun alone.

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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › Checks job structure is red on 9 of 11 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › No new SwiftUI (macOS UI is AppKit) is red on 9 of 11 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next fast gates / post cmux-next/fast-gates is red on 19 of 20 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

teamleaderleo and others added 2 commits October 26, 2026 03:27
… 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>
@cursor

cursor Bot commented Oct 26, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 26, 2026 03:28 — with GitHub Actions Inactive
@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:high Risk tier (docs/ci/risk-tiers.md) labels Oct 26, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit ac5f1e9 into feat-cmux-next Oct 26, 2026
213 of 222 checks passed
@teamleaderleo
teamleaderleo deleted the sp-usage-limits branch October 26, 2026 05:04
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

This branch had an error being deployed

2 failed and 2 inactive (1 outdated) deployments
artifacts — 2ccfca34 Deployed Oct 26, 2026 by teamleaderleo via publish the Windows daemon to R2 #54527
sentry-dsyms — 2ccfca34 Deployed Oct 26, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #54527
cmux-tui-merge-tree — 2ccfca34 Deployed Oct 26, 2026 by teamleaderleo via publish the merge tree #6071
ci-routing-agent — 3f4e6777 Deployed Oct 26, 2026 by github-actions[bot] via cmux-next macOS placement #35608
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant