Skip to content

fix: onboard finishes the sign-in when the token can't be saved, and the CLI says where it runs - #169

Merged
bitpshr merged 6 commits into
paul/windows-loopback-fixfrom
paul/onboard-server-error
Oct 22, 2026
Merged

bitpshr merged 6 commits into
paul/windows-loopback-fixfrom
paul/onboard-server-error

Conversation

@bitpshr

@bitpshr bitpshr commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #167. It changes the hand-written sign-in that #167 adds.

Why

Some onboard sign-ins end on the server_error page even though the browser reached the CLI. From what I can tell there are two paths, and this PR fixes the first:

  • The exchange worked, saving the token failed. server_hosted_mcp_oauth_granted fires inside /v1/oauth/token after a successful exchange. In 11 of the 24 onboard server_error pages since Oct 19 (8 people, mostly Mac), it fired 0.6–2 s before the error page. Once the exchange has worked, the only remaining step that can fail is active_store().set(…). It probably fails because the Keychain refuses the binary: release builds aren't codesigned, so every upgrade counts as a new app for an existing elevenlabs entry. A sandboxed home folder would fail the same way. onboard only uses that token for a single GET /v1/user check, so failing the whole sign-in here is unnecessary.
  • The exchange itself failed (no grant, 13 pages). Not fixed here, because the cause isn't clear yet. More on that below.

What changes

  • loopback::sign_in returns the access token. If saving it fails, the reason goes to the worker log and the browser gets the success page.
  • key_matches_sign_in takes the token directly instead of reading it back from the store.
  • auth login (Fern's flow) is unchanged.

One consequence: after a failed save, the key is in the env file and everything in onboard works, but commands that use the stored sign-in don't see this one. They keep whatever was stored before, so they may still act as the previous account.

Also: where the CLI runs (folded in from #168)

From the API's side today, a CLI request is just elevenlabs-cli/<version> cmd/<command>, so a problem like the Windows sign-in misses in #167 can't be told apart from WSL, containers or SSH. The hand-written commands now append os/<windows|macos|linux|other>, plus env/<ssh|container|wsl> when one applies, after their cmd/ tag. Only whether the relevant environment variables are set is checked, and their values are never sent.

onboard also passes cli_os and cli_env on the approval-page link. A sign-in whose callback never reaches the CLI makes no API call, so the page's events are the only place those misses can be counted per platform. SSH and container detection now lives in one place, a small workflow/platform.rs, which the browser check reuses. Its behavior is unchanged. The tag also counts Podman (/run/.containerenv) and Kubernetes pods (KUBERNETES_SERVICE_HOST), but the browser check doesn't: toolbox and distrobox are Podman containers that share the host's network and display, and telepresence copies a pod's environment onto a laptop. The xi side, which reads the tags on the backend and the link parameters on the page, is elevenlabs/xi#58352.

Also: a sign-in that works but never checks the key

Some sign-ins reach the success page, but the CLI never sends its GET /v1/user check. Since Oct 20 that's 8 of about 1,000 Windows hand-offs (7 people), against 1 of about 1,000 on Mac. None of them used the copied key: four made a new key by hand and ran onboard check, which is the manual path ("no key reached this command"), and one ran connect again 12 minutes later and it worked.

From what I can tell it's the clipboard read. On Windows, arboard tries to open the clipboard six times, 5 ms apart, then gives up with ClipboardOccupied, and onboard treated that the same as nothing copied. Clipboard managers, remote desktop and sync tools hold the clipboard for short moments. (The other way to get here in 1.7.0, a token that couldn't be read back from the store, is already gone with the change above.)

  • The hand-off read now tries again for up to 2 s, every 100 ms, until it finds a key or a marker. That also covers a browser copy that lands late. Clearing a key after a failed sign-in still reads once.
  • If the clipboard never opens, the reply gets its own key_status, clipboard_unreadable, and says the copied key may still be on the clipboard, so the developer can paste it instead of creating another. An empty clipboard is still no_key.
  • Clearing the key after storing it retries the same way, so "your clipboard was cleared" stays true.

One case this can't fix: Chromium's own clipboard write gives up just as quickly and fails silently, while the page still reports key_copied. If that's what happened, the CLI reports no_key after 2 s, as before.

Testing

  • New a_sign_in_the_credential_store_refuses_still_finishes: a file sits where the file store needs its folder, so the save fails. Without the fix the browser gets the exact production redirect (cli/error?error=server_error…). With it, the browser gets the success page and the worker finishes.
  • New unit test a_clipboard_key_is_checked_with_the_token_the_sign_in_returned: the key check uses the token sign_in returns. It fails if the check reads the store again or sends a different token.
  • onboard_test 26/26 and bin 190/190 on this branch. No new clippy warnings. The new lines are rustfmt-clean (onboard/tests.rs and tests/onboard_test.rs already had formatting differences, which I left alone). The Windows target type-checks.
  • Platform: the platform::detect_for, authorize_params and browser-reachability tests, the command_scope tests (the tag after cmd/), and an onboard integration check that the approval link carries this machine's cli_os. That check fails if the link stops carrying it or reports the wrong OS.
  • Not tested live against a Keychain that refuses the CLI.
  • Clipboard: new unit test the_hand_off_read_waits_out_a_busy_or_late_clipboard (a busy clipboard then a key, a marker that lands late, never readable versus read but empty). clip_of maps arboard's errors (busy is Unreadable, no text is Nothing), and outcome_of maps Unreadable to clipboard_unreadable. Not tested against a real held clipboard on Windows.

Still open: the no-grant path

The error page records only error, and the worker's reason stays in its log, so PostHog can't separate these cases. Timings (handoff to error page) fall into rough buckets: about 1 s (instant), about 2 s on Windows, 5.5 s on Linux every time (looks like DNS), and about 10.5 s (the 10 s connect timeout). The token URL is api.us.elevenlabs.io, a different host from the rest of onboard, so a network or sandbox allowlist could block it alone. I haven't verified that. I think the next step is to send a coarse failure stage to the error page and record it, or check the backend logs for POST /v1/oauth/token 4xx at those times.

@bitpshr
bitpshr requested a review from PaulAsjes October 22, 2026 12:54
@bitpshr
bitpshr added this pull request to stack #172 October 22, 2026 12:56
@bitpshr
bitpshr marked this pull request as ready for review October 22, 2026 12:56
@bitpshr bitpshr changed the title fix: onboard finishes the sign-in when the token can't be saved fix: onboard finishes the sign-in when the token can't be saved, and the CLI says where it runs Oct 22, 2026
@bitpshr
bitpshr force-pushed the paul/onboard-server-error branch from 19ae814 to c1f9a54 Compare October 22, 2026 13:09
The token exchange can succeed while saving the token fails (a keychain
that refuses the CLI, a sandboxed home folder). The browser then landed on
the server_error page and the agent was told the sign-in didn't finish,
even though onboard only needs the token for one account check.

Keep the token in memory for that check, note the failed save in the
worker log, and send the browser to the success page.
Also say plainly what a failed save leaves behind: commands that read
the stored sign-in keep the old one, which may be another account's.
…luded; test the platform on the approval link
@bitpshr
bitpshr force-pushed the paul/onboard-server-error branch from c1f9a54 to 1fbb6da Compare October 22, 2026 13:46
On Windows a clipboard read gives up after about 30 ms when another program
has the clipboard open, and the sign-in then told the developer no key had
been copied. The hand-off read now tries again for up to 2 s, which also
covers a browser copy that lands late, and so does clearing the key once it
is stored. A clipboard that never opens gets its own status,
clipboard_unreadable, and the reply says the copied key may still be on the
clipboard.
@bitpshr
bitpshr merged commit 0a99233 into main Oct 22, 2026
17 checks passed
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