feat(runtime): pod-side Claude login relay — mint the subscription credential on the engine pod (PRODUCT-1136) - #1272
Open
cravenceiling wants to merge 2 commits into
Open
feat(runtime): pod-side Claude login relay — mint the subscription credential on the engine pod (PRODUCT-1136)#1272cravenceiling wants to merge 2 commits into
cravenceiling wants to merge 2 commits into
Conversation
…dential on the engine pod (PRODUCT-1136) Connect Claude with zero local install: the runtime spawns the pod's own `claude auth login --claudeai` (the desktop runner's production-proven piped readline contract), relays the authorize URL over the existing auth_code LoginInfo, feeds the pasted approval code back to the CLI's stdin, and sinks the minted credential where the sanctioned flows expect it — auth.json for the connect-once capture (gateway stays the family's single rotator) plus the team-scope materialized file (access-only in serve mode). The mint happens in a throwaway temp dir outside the store-sync tree, scrubbed win or lose. Where the CLI cannot run (binary stripped from the cloud per-turn image, macOS Keychain mint, HOUSTON_CLAUDE_POD_LOGIN=0 kill switch, or a child that dies/goes silent before printing its URL — the probed Ink-TUI deadlock shape) the flow degrades to the setup-token paste dialog, which stays the terminal fallback. Post-URL failures surface as real sign-in errors; the dialog never switches under the user. The auth_code dialog copy goes flow-neutral and the link is labeled by its destination host — it is the primary action for the relay, still the docs reference for the fallback.
…-1136) - A clean CLI exit that never printed an authorize URL is now fallback-eligible instead of a false success: with a fresh mint dir there is no cached session, so exit 0 without a URL cannot be a mint — resolving stored nothing while startLogin's info race timed out on a dialog that never opened. - The pasted-code stdin write is guarded against a synchronous throw racing the child's death, so the floating then-chain can never carry an unhandled rejection. - A failed mint-dir removal now logs the path loudly (never contents): a self-host /tmp outlives the process, and a lingering mint holds a refresh token.
Collaborator
Author
|
Follow-up commit from an independent review pass: pre-URL clean exits are now fallback-eligible instead of a false success, the stdin paste write can't float an unhandled rejection, and a failed mint-dir removal logs loudly. Two new tests pin the first two. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes PRODUCT-1136.
Why
Connecting a Claude subscription previously required minting the OAuth credential on the user's machine: the desktop spawns a bundled
claudehelper (which SIGILLs outright on pre-AVX2 CPUs — Sentry HOUSTON-APP-543, 103 events) and then pushes the credential to the pod, a handoff that fails in transit for real users (push_claude_oauth_credentialfamily: ~101 events / ~50 users in 4 days, with HOUSTON-APP-564's 19 events landing users in a paste flow whose instructions require installing the Claude CLI). The product requirement is: users install nothing.What
The runtime now mints the credential on the engine pod by driving the pod's own
claude auth login --claudeai— the same binary every turn already resolves, and the same piped-readline child contract the desktop runner has proven in production.backends/claude/login-cli.ts— subprocess + parsing mechanics: spawn withCLAUDE_CONFIG_DIRpointed at amkdtempmint dir (outside the store-sync tree — the sync excludes are exact-key, so a mint underHOUSTON_HOMEwould leak to the object store on older hosts),visit:-marker URL extraction with OSC-8 stripping, envelope read of<mintDir>/.credentials.json.auth/anthropic-cli-login.ts— the flow driver: the authorize URL rides the existingauth_codeLoginInfo (zero protocol change, zero cloud-repo change), the pasted approval code answers the CLI'sPaste code here if prompted >readline, and the minted credential is sunk exactly where the sanctioned flows expect it: a full entry in the acting scope's auth.json (so the client's existing connect-once capture → central store put → refresh scrub makes the gateway the family's single rotator, trap Landing page refresh: hero animation, Mission Control, nav polish #4), plus the team-scope materialized file exactly like a desktop push (access-only in serve mode; personal scope never touches the pod-shared file, HOU-976). The mint dir is scrubbed win or lose.auth/login.ts— the anthropic seam now runs the relay and degrades to the setup-token paste flow only when the relay is unavailable on this pod: binary absent (the cloud per-turn image strips it), macOS (Keychain mint, unreadable),HOUSTON_CLAUDE_POD_LOGIN=0(kill switch), or a child that dies/goes silent before printing its URL — which also deterministically catches the feared Ink-TUI piped-stdio deadlock. Every unavailability is pre-URL by construction, so the dialog never switches under the user; post-URL failures (declined approval, bad code) surface as real sign-in errors. Cancellation is never misclassified as fallback.provider-login-dialog.tsx+ en/es/pt locales) — theauth_codeheader goes flow-neutral and the link is labeled by destination host: it is the primary action for the relay, still the docs reference for the fallback.No client routing changes needed: web "Connect Claude" and the PRODUCT-1135 desktop degrade path already call
providerLogin→ runtimestartLogin, andpollProviderConnectalready captures + scrubs on completion.Net UX on any hardware
Click Connect → approve in the browser → paste the one code claude.ai shows. Nothing installed.
Tests
login-cli.test.ts(URL/OSC-8/envelope parsing) andanthropic-cli-login.test.ts(fake-child driver: happy path, ENOENT/silence/pre-URL-exit fallbacks, post-URL decline/signal as real errors, abort never falls back, mint scrub on every path, sink serve/self-host/personal-scope matrix).login.test.tspins its anthropic tests to the setup-token path via the kill switch so no machine's realclaudeis spawned.tsgotypecheck both packages, biome clean on touched files.Before / after
Reproduce the gap before this change (as a cloud/web user, or any desktop where the helper can't run):
claude setup-tokenin your terminal…" — i.e. install something. That is the entire fallback surface for pre-AVX2 desktops (HOUSTON-APP-543) and for credential-push failures (HOUSTON-APP-564).Verify after deploy (engine image containing this change):
Open claude.comlink and a paste box.<HOUSTON_HOME>/claude-login/.credentials.jsonexists (refresh-stripped under serve mode), auth.json's anthropic refresh token is scrubbed after capture, and noclaude-login-mint-*dir remains in/tmp.HOUSTON_CLAUDE_POD_LOGIN=0on the runtime (or use an image without the SDK binary) → the same click lands on the setup-token dialog, previous behavior.