Repository navigation
fix(rate-limits): stop driving a hidden Claude to read usage - #24302
brennanb2025 wants to merge 5 commits into
Conversation
Claude usage fell back to a hidden interactive `claude` that typed /usage, pressed Enter every 0.8 s without reading the screen and answered "y" to anything that looked like a trust prompt. On a login picker that Enter starts a browser sign-in. It ran for system-default logins too: whenever the usage reply had no Fable window, on every poll. Every fallback whose result could not differ from the direct usage read is gone (Fable fill-in, inactive-account preview fill-in, server/network, unreadable Keychain, no credentials), together with the hidden PTY, its screen parsers and the preview Keychain staging. The one case that needs Claude itself, an expired login on an Orca-managed account with no Claude running, now starts Claude headless, never sends a message, and asks for plan usage so Claude refreshes its own login under its own lock. Orca ignores that answer, re-reads the stored login and asks the usage endpoint itself; an unchanged login backs off for 15 minutes and a CLI without get_usage is not started again until the binary changes. A managed account with no login now reports itself as signed out.
…sage-no-blind-keys
… and say when a login needs the user - Waiting out an account switch no longer starts Claude on whatever login the switch left in the shared Claude home (possibly the user's own): the refresh re-reads the selected account, bails on a change or an abort, and never starts Claude. - The 15-minute backoff is only set when Claude actually ran; a refresh Orca never started backs nothing off. - After Claude ran and the stored login is still expired, the meter says "Sign-in expired" and how to renew, instead of "Refreshing sign-in" forever. - A managed account with no login says "Signed out" and points to re-authentication, instead of "Refresh failed".
…ame the real Settings page - A Claude binary that is missing or can't be executed fails asynchronously, after the connection opens. The refresh now reads the connection's processless verdict and reports "not launched" instead of a failed login refresh: the meter says usage is unavailable rather than "Sign-in expired", and a 15-minute per-binary backoff stops a spawn attempt every poll. - The signed-out and renewal tooltips point to Settings → AI Provider Accounts (the page's real title, localized from the existing sidebar translation) instead of a nonexistent "Accounts". - Fix a stale comment that still described the removed hidden-terminal gate.
…h launches it On macOS and Linux the launch goes through a supervisor process that always starts, so a missing Claude binary only showed up as the supervisor exiting, never as the "processless" verdict the previous fix read. The refresh now checks the resolved command before launching: a bare name (the resolver found nothing) or a path that is not an executable file is reported as not launched, without spawning anything, and keeps the per-binary backoff and the "usage unavailable" reporting. The after-the-fact verdict read and its extra close() are gone, so each connection is closed exactly once.
Review status: ready for a human look, still a draftThe problem. To read Claude plan usage, Orca could start a hidden interactive What changes for users.
Fixed during review (head
Deferred
Verified
Not verified
CI. |
|
Closing as obsolete: main no longer has the code this PR changes, or already behaves as it intended. Main no longer starts a hidden Claude to read usage. 8b2a5e4 (#26801) deleted the hidden-Claude terminal and CLI usage fetch code (claude-pty.ts, claude-cli-usage-fetch.ts, claude-oauth-recovery.ts and others). Main's src/main/rate-limits has no claude-pty or CLI fetch, only the OAuth usage (Stale-PR cleanup 2026-10-20, approved by Brennan. Reopen if something here is still missing on main.) |



Fixes: Orca's background Claude usage check could start a hidden interactive Claude and press keys without looking at the screen: Enter every 0.8 s, and
yon anything that looked like a trust prompt. On a login screen, that Enter starts a browser sign-in nobody asked for. Same bug class as #17415 (fixed for Codex in #23806).Scope: the Claude usage meter (status bar, usage menu, account previews). The hidden Claude only ever ran on macOS and Linux, so that is where the behavior change is. On Windows, one wording change applies too (see "Signed-out accounts" below). Terminals, chats and every other provider are not affected.
ELI5
To read your Claude plan usage, Orca sometimes opened a hidden Claude window, typed
/usage, and kept pressing Enter blindly. If Claude was showing something else, like a login picker, those keypresses chose options for you. Orca now never opens Claude's interactive screen. It reads usage straight from Anthropic. When an Orca-managed account's login has expired and Orca's own renewal didn't fix it, Orca starts Claude in its headless mode, asks it one question so it renews its own login, and closes it. If that doesn't work either, the meter now tells you to renew the login instead of saying "Refreshing sign-in" forever.What Changed
Before: the hidden interactive Claude ran in four situations:
After:
limitslist Claude's own/usagepanel reads, which Orca already parses. Server, network and Keychain failures show the error, and the last good numbers stay for up to 30 minutes.get_usagerequest. Claude renews its own login under its own refresh lock and saves it to its own store.get_usageis not started again until the binary changes.Deleted: the hidden-terminal probe (
claude-pty.ts), its screen parsers and stop markers, the Fable and preview fill-ins, the preview Keychain staging, the "oauth then cli" refresh plan, and the hidden-terminal cleanup registry.Why
Reading usage from a screen with blind keypresses can't be made safe: the probe can't know which screen it's answering. Each removed fallback either gets the same data Orca already has (Claude's
/usagereads the same endpoint, and reads the Keychain with the samesecuritytool) or lands on the login picker. An expired login that Orca's own renewal couldn't fix is the one case where Claude can do something Orca can't: renew under its own lock, so it never races a Claude the user started outside Orca. So that case still asks Claude, but through its headless interface, where no prompt can appear.Alternatives considered:
Mozilla/5.0User-Agent Electron sends, so Orca's renewal may be failing today. That is a separate bug in existing code and gets its own follow-up. Even once fixed, Orca's renewal doesn't take Claude's refresh lock, so it can race a Claude started outside Orca.Differences from how comparable apps do this:
get_usageanswer and reads the endpoint itself, since Claude may answer from a snapshot up to 60 s old.config/relay-assets/node-pty-1.1.0-windows-pty-teardown-patch.cjsstill names the deletedclaude-pty.ts. That file is a hashed relay asset, and any byte change makes every SSH host reinstall its relay, so it's left alone. The non-hashed test comment that said the same thing is updated.Linked Issue
Follow-up to #17415 / #23806 (same bug class, Claude instead of Codex).
Visual Proof
N/A for now: the meter's layout is unchanged. The new text is "Signed out" (managed account with no login) and "Sign-in expired" (Claude couldn't renew the login), both with tooltip guidance, and localized in every shipped language. Live Electron QA of these states is pending (see Testing).
Testing
~/.claude: the headlessget_usagerequest was answered in about 1.2 s without any message being sent. The child exited cleanly, no transcript was written, and the transcript scan was skipped (behaviors: null). With no login it reportedrate_limits_available: false, with no prompt and no error.skip_behaviors..oauth_refresh.lock, saved by compare-and-swap) was read from the 2.1.280 binary. Also not tested live: Electron UI, Linux, WSL, SSH.Notes
delegated-refresh-requiredfailure kind (already used by Grok and Kimi). An older Orca window that reads it shows "Refreshing sign-in", which is what it showed before. Mobile and the CLI don't read failure kinds. The'cli'source value is kept.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)