Skip to content

fix(cli): keep browser sign-in open through checkout - #6879

Open
vanceingalls wants to merge 3 commits into
mainfrom
fix/cli-login-checkout-timeout
Open

vanceingalls wants to merge 3 commits into
mainfrom
fix/cli-login-checkout-timeout

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

Problem

hyperframes auth login waits for the browser on a loopback listener that gave up after 5 minutes. The HyperFrames paywall now runs inside that OAuth flow (plan choice, Stripe checkout, return trip), so a slower purchase ended with the CLI printing Sign-in failed: OAuth callback timed out after 300000ms. The browser's Authorize step then redirected to a dead port and showed ERR_CONNECTION_REFUSED. Seen in production on 2026-10-31 on CLI 0.8.223.

Change

  • Listener default 5 min → 60 min (loopback.ts). Pacific keeps the OAuth plan-checkout handoff for 60 minutes (MAX_AGE_MS, planCheckoutHandoff.ts), so a shorter limit leaves the same failure for slower checkouts.
  • The waiting line states the limit and Ctrl+C. After a timed-out browser login only, the CLI prints Run `hyperframes auth login` again.
  • Stale 120s comments corrected; docs/packages/cli.mdx mentions the window.

The 300s value came in with #6754 (re-landed as #6833); neither commit message gives a reason for it. Flagging in case its author knows one.

Cost

An abandoned login now blocks its terminal for up to an hour (Ctrl+C ends it).

Not in this PR

  • --device flow: its page has no checkout code, so no paywall wait.
  • Whether a purchase made during a timed-out login shows on the account: unverified, so the CLI makes no claim about it.

Tests

  • New fake-timer tests: callback just before 60 min succeeds and leaves no timer behind (fails if the cleanup in startLoopback is removed); timeout fires at exactly 60 min, not before.
  • Login copy: recovery line appears for timeouts only.
  • src/auth + src/commands/auth: 238 pass. Full packages/cli run: 8 failures (checkSweep WebGL ×2, fileWatcher.atomic, studioServer.stampOnOpen, whisper manager.install, sherpa ×3), the same 8 fail with the original source and none import auth.
  • Manually: built CLI shows the new waiting line, exits on Ctrl+C, frees the port.

🤖 Generated with Claude Code

vanceingalls and others added 3 commits October 31, 2026 00:46
The loopback listener timed out after 5 minutes, but plan checkout runs
inside the OAuth flow. A slower purchase ended on ERR_CONNECTION_REFUSED.
Raise the default to 60 minutes, matching pacific's plan-checkout handoff.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The waiting line now states the 60-minute limit and Ctrl+C. After a
timed-out browser login the CLI says to run auth login again.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… limit

Review fixes: the timeout hint no longer says purchases stay on the account
(the CLI cannot observe billing); the no-timer-left test now asserts before
any explicit close() and uses node:http so undici timers cannot pollute the
count; docs mention the 60-minute window and Ctrl+C.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 31, 2026, 8:11 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 664e8061d: no blockers. The change does what it says. I have two nits, plus a few notes on the "hardest look" points.

What I checked

  • Callers. startLoopback has exactly one caller, startAuthorizationCodeFlow (oauth.ts:148). Nothing passes timeoutMs in production code, so the new default only affects hyperframes auth login (browser). --device and --api-key don't touch it.
  • Server side. Nothing server-side expires sooner than the new window. /v1/oauth/authorize carries state and code_challenge in the URL, and the auth code is minted at grant time, which is after checkout. So no server TTL runs during the wait. Pacific's planCheckoutHandoff.ts keeps its sessionStorage handoff for MAX_AGE_MS = 60 * 60 * 1000, so 60 min on the CLI side is the right match. One caveat: pacific's clock starts when checkout is marked, while the CLI's clock starts when the command runs. So the CLI is always the shorter of the two, by however long sign-in and choosing a plan took. That's fine; the CLI won't keep listening after pacific has given up.
  • Where the 5 min came from. git log -S300_000 points at #6754 (cb2389cf3). It raised the value from 120s to 300s as part of the reverse-any-video and billing work, with no stated reason. The JSDoc still said "120s" until this PR. I don't see a security reason to keep it short. The listener binds to 127.0.0.1 on an ephemeral port, rejects non-GET requests and unknown paths, and checks state. A longer window only means a stale listener sits on a local port longer, and PKCE plus state still protect it.
  • The abandoned login blocking the terminal. The setTimeout keeps the process alive for up to 60 min, but Ctrl+C exits cleanly, and the new waiting line says so. The media-use skill tells agents to have the person run ! npx hyperframes auth login, so a human is at the keyboard. An agent calling it through a Bash tool is capped by that tool's own timeout. Acceptable.
  • Cleanup on success. The new fake-timer test asserts vi.getTimerCount() === 0 after the result resolves and before close(). That pins the behaviour that matters most now: with an hour-long timer, a leaked timer would keep the CLI alive for the full hour after "Signed in". result.finally(close) → clearTimeout(timer) makes that hold. The boundary test (-1ms not settled, +1ms rejects) is also good.
  • Recovery line. The timedOut regex is unchanged from the telemetry split, so flow_timeout and flow_error behave as before. The negative test (CSRF mismatch → no recovery line) is a good touch.

Nits

  1. The waiting line hardcodes the window (oauth.ts:166-168). It prints "up to 60 minutes" even when opts.timeoutMs is overridden. No production caller overrides it today, but deriving the text from the effective timeout, or exporting DEFAULT_TIMEOUT_MS and formatting from it, would keep the message and the timer from drifting apart the next time someone changes one of them.
  2. The docs line says "If it times out, run it again." It could also mention --device for SSH or headless setups where the browser can't reach the loopback port, since that's the other common "never called back" case. Optional.

Not verified

  • A real checkout taking more than 5 minutes end to end. Same gap as the PR states.
  • Whether a purchase made during a timed-out login is visible on the account. The PR doesn't claim this either, and I agree with not printing anything about it.
  • CI was still running when I reviewed. I'll check it before anyone asks for an approve.

— Somu

This branch was successfully deployed

1 active deployment
staging - docs — 664e8061 Deployed Oct 31, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants