feat(chat): a usage limit pauses the queue and offers to resume at the reset
Merged
AV
10d ago+3145+3145+0123456789012345678901234567890123456789-43-43-01234567890123456789
Walkthrough: https://app.superset.sh/page/usage-limits-in-chat-z1o65a — screenshots from the desktop dev app, including the loop end to end and the three fixes below.
Replaces #8439. #8439 was stacked on #8438, so merging it first squashed it into #8438's branch instead of main; that was reverted there (8c1947816b), #8438 landed on its own, and this PR brings the usage limit change to main by itself.
What
When the provider refuses a turn for the account's usage:
- The transcript marks the stopped turn with one quiet line, Stopped by the usage limit, like "Stopped" for an interruption. A usage limit is a pause, not an error, so there is no red card and no actions that would hit the same limit.
- A notice above the composer names the window (5-hour limit reached, Weekly, Weekly Opus or Sonnet, Extra usage, or "Usage limit reached"), the model, the login this chat runs on, and the reset ("Resets in 2h 14m · 5:15 AM", ticking). Its controls: the login menu (the host's logins for the agent with plan and tightest window; picking another makes it the default and reopens the chat on it, queued prompts included; plus "Usage settings"), Resume / Resume at reset (→ "Resuming at reset", click again to cancel), and dismiss.
- The queue pauses, and prompts sent while limited wait instead of hitting the wall again. The notice is the top row of the work drawer, with the waiting prompts under a divider, so the notice, the queue and the composer read as one panel. While the notice speaks for the queue, the separate "Queue paused" bar is left out; where that bar does show for a limit, it says Queue paused by the usage limit.
- After the reset, an armed resume waits the host's grace before it sends, and the notice counts down: Limit has reset · Resuming in 26s. It then sends the next queued prompt, or "Continue where you left off." when nothing is queued.
- While no limit is hit, main's early warning ("5-hour limit 80% used") shows as before; the two never show together.
How
- Protocol (
@superset/chat):SessionState.usageLimit?: { hitAtMs, resetsAtMs?, resumeAtReset?, resumesAtMs?, window?, model? }, arespondToUsageLimit({ action })command (resume | dismiss | resume_at_reset | cancel_resume_at_reset), and ausage_limitturn failure kind. All additive: older clients ignore the fields, and an unknown failure kind already falls back to "unknown". - ACP adapter: reads
usage_update._meta(_claude/rateLimit: arejectedwindow not on overage,resetsAtseconds → ms,rateLimitType; and_claude/model). When the prompt is then refused it reportsusageLimitbefore the failed turn and tags the turn errorkind: "usage_limit". Legacyclaude-codesessions get the same fromrate_limit_event. Any harness whose turn fails with the limit in its error text gets the fallback without a reset time. - Live session: a limit pauses the queue;
resumecontinues the cut-short work;resume_at_resetarms a host-side timer (reset + 30 s grace) and reportsresumesAtMs; cancel clears it;dismissdrops the notice and keeps the queue paused. A dead or closed session cancels the timer. - Renderer:
SessionViewrendersUsageLimitNoticeas theWorkDrawer's newnoticerow (1 s tick during the grace countdown); the composer only hides the early warning while limited.TurnStatusRowdraws ausage_limitfailure as a muted line instead of the failure card.QueuePausedBartakesbyLimit.AcpChatPanerestarts the chat on the chosen login with its queued prompts restored. - 20 new strings in all 16 locales.
Verified
- chat-runtime 426, chat 103, desktop ChatSession + ChatPane + AgentComposer 560; tsc for chat, chat-runtime, desktop and host-service; Biome;
check:i18n(0 missing). - Regression tests for the three latest fixes fail on the code before them.
- Redesigned after review (quiet history line, one panel): checked again live; before/after in section 6 of the walkthrough.
- End to end in the dev app with a scripted ACP agent that refuses the way Claude does (
_claude/rateLimit: rejected, then "You've hit your usage limit"): the failure card and notice, the login menu, a prompt held while limited, Resume at reset armed, the countdown after the reset, and the host sending the queued prompt 30 s later. Screenshots on the walkthrough page. - Not exercised: actually switching to another login (it changes the machine's default login), and a real Anthropic rate limit.
Summary by cubic
The usage limit now pauses the chat, holds queued prompts, and offers to resume immediately or at the reset, instead of failing the turn and sending queued prompts into the same wall.
Behavior
- The failed turn shows as one muted "Stopped by the usage limit" line with no failure card or actions, like a stopped turn; a v2 turn that fails through a later error state gets the same treatment.
- The notice leads the work drawer above the composer, naming the window, model, login, and a ticking reset countdown. The countdown is not in the live region — the title is announced once — and the row and controls wrap in narrow panes.
- Controls are Resume, Resume at reset, dismiss, and an account switch; with nothing queued the notice is the whole panel.
- An armed resume sends the next queued prompt (or "Continue where you left off" when empty) 30 seconds after the reset; an account switch reopens the chat with the full queue restored, hidden parts included.
- Early-warning banners still show when no limit is hit; the two never appear together. While the notice speaks for the queue the separate "Queue paused" bar is left out; once the notice is dismissed, the bar names the limit.
Protocol
SessionState.usageLimit, therespondToUsageLimitcommand, and theusage_limitfailure kind are additive, so older clients ignore them.- The ACP adapter reads
_claude/rateLimitfromusage_update._meta; the stream translator keeps refusedrate_limit_events, and any harness whose turn fails with the limit in its error text gets a fallback without a reset time.
Summary by CodeRabbit
- New Features
- Chat sessions now recognize provider usage limits and show the affected model, reset timing, and resumption status when available.
- Queued prompts pause when a limit is reached. Resume immediately, schedule resumption for the reset, cancel a scheduled resume, or dismiss the notice.
- Where supported, switch accounts from the usage notice and continue the chat with queued prompts preserved.
- Bug Fixes
- Usage-limit stops now have a clear status message instead of a generic failure card.