[BUG] Rate-limit dialog blocks the session with a single option whose only effect is dismissal
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a usage limit is hit, the rate-limit-options dialog opens. On my account it renders with exactly one option, and selecting that option does nothing that Esc doesn't already do — so the session blocks on a keypress to resolve a choice that has no branches.
Reading the shipped bundle for 2.1.220 (identifiers below are mangled by minification), the cancel entry and the dialog's onCancel are the same handler:
X5t = function(){ M("tengu_rate_limit_options_menu_cancel",{});
W9e(undefined, {display:"skip"}) }
Oni = function(v){ /* … */ else if (v === "cancel") X5t() }
// dialog: { title: "What do you want to do?", onCancel: X5t, … }
Enter on option 1 and Esc both call W9e(undefined, {display:"skip"}).
This is the degenerate case of the menu. The other entries are appended only when the server's upgradePaths includes them:
if (paths.includes("overage")) opts.push({ value: "extra-usage", … })
if (paths.includes("upgrade_plan")) opts.push({ value: "upgrade", … })
const cancel = { value: "cancel",
label: billingType === "usage_based"
? "Stop"
: "Stop and wait for limit to reset" }
When upgradePaths includes neither, only cancel survives — and the dialog opens anyway, with nothing in it to decide.
Why this is more than cosmetic. The dialog requires a keypress, so an unattended session halts there. I run long-lived sessions driven by recurring scheduled prompts; ordinary transient errors print to the console and the next tick retries, but this modal stops the session until a human is present — to answer a question that has one answer, which does nothing.
What Should Happen?
When the option list reduces to only the cancel entry, don't open a modal. Print the notice (limit reached, resets at <time>) and let the session continue without input.
A dialog should block only when there is a decision to make.
Error Messages/Logs
You've hit your limit · resets <time>
What do you want to do?
❯ 1. Stop and wait for limit to reset
Enter to confirm · Esc to cancel
Steps to Reproduce
- Sign in with an OAuth subscription account (not usage-based billing) whose server response returns neither
overagenorupgrade_planinupgradePaths. - Run an interactive session until the "usage limit" is hit.
- The dialog opens with a single option, and the session blocks until a key is pressed.
- Press Enter on option 1, then repeat and press Esc instead. Both produce the same result — the dialog dismisses and nothing else changes.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.220
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
Not a duplicate of:
- #13354 / #35744 (auto-continue after the limit resets) — I am not asking for auto-continue or auto-resume. Only: do not open a modal that has no actionable option.
- #40898 (closed NOT_PLANNED) — that request concerned reordering and adding options on a three-option menu. This is the zero-actionable-option case, which it did not cover.
A suppression carve-out already exists, but users cannot reach it. The dispatch is guarded by:
if (AN.current) return false; // once-per-session latch
if (LR() || rs()) return false; // repl bridge active, or background session
function rs(){ return Got() === "bg" }
function Got(){ const e = env.CLAUDE_CODE_SESSION_KIND;
if (e === "bg" || e === "daemon" || e === "daemon-worker") return e; }
Background sessions are already exempt. But CLAUDE_CODE_SESSION_KIND is an internal role marker, always set as part of a bundle (CLAUDE_BG_BACKEND, CLAUDE_JOB_DIR, CLAUDE_BG_AUTH_SNAPSHOT_PATH), so setting it by hand on a foreground session to dodge the dialog means running in a half-configured background role. There is no user-facing equivalent.
Adjacent observation, not the bug being filed: Got() recognizes daemon and daemon-worker, but rs() compares strictly to "bg", so daemon-worker sessions are not exempt. That reads unintentional.
Context on the open path: the v2.1.90 changelog records a fix for "an infinite loop where the rate-limit options dialog would repeatedly auto-open", which appears to be why the AN.current once-per-session latch exists — so this dialog's open path has been revised before.