Stop routing headless setup onto legacy GitHub App auth - #536
Conversation
A non-interactive `kcap setup` / `kcap login` with no --server-url used to fall back to the GitHub App provider, because GitHub offers a device flow and WorkOS authenticates through a 127.0.0.1 loopback callback that a browser on another machine can never reach. That fallback was the only remaining source of NEW GitHub App sign-ins — CLI telemetry since the funnel went live shows all 6 interactive devices on WorkOS and the single GitHub App device headless — and it dead-ended. The GitHub App branch has no provisioning path by design (it discovers orgs you already belong to), so a headless user with no workspace completed a device-code sign-in and was then told to ask an admin to install a GitHub App. One such user retried the whole flow and gave up. Improving that copy would have advertised a provider we are migrating off, so the routing goes instead. ChooseDiscoveryProvider now returns null for a non-interactive session, and both call sites report HeadlessDiscoveryUnsupportedMessage and stop — naming the two routes that work (create a workspace in a browser, or pass --server-url) and no GitHub App. The check moves ahead of the auth-service call in both, so the run fails in ~2s instead of after a full device-code dance it cannot use. Deliberately narrow: this affects discovery only. Existing GitHub App workspaces still work via --server-url, where the provider comes from that server's /auth/config, and --github remains an explicit escape hatch that still works headless. Giving WorkOS a real headless path is issue #535. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR Summary by QodoFail fast for headless discovery; remove legacy GitHub App auth fallback
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 080263e8c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (args.Contains("--github")) return AuthProvider.GitHubApp; | ||
|
|
||
| return isInteractive ? AuthProvider.WorkOS : AuthProvider.GitHubApp; | ||
| return isInteractive ? AuthProvider.WorkOS : null; |
There was a problem hiding this comment.
Honor
--device before rejecting headless discovery
For a headless discovery invocation with no configured profile, such as the documented kcap login --device or kcap setup --device, ShouldDiscoverLogin routes here but this logic ignores --device and returns null, so the command exits before starting GitHub Device Flow. This regresses the CLI contract in help-login.txt:31-33, help-setup.txt:13-14, and README.md:1850, which explicitly recommends --device for SSH/headless environments; either treat --device as an explicit GitHub selection or update the accepted syntax and guidance to require --github --device.
Useful? React with 👍 / 👎.
Code Review by Qodo
1.
|
Codex review on #536 caught a regression: dropping the implicit headless fallback also broke `kcap login --device` / `kcap setup --device`, which three docs (help-login.txt, help-setup.txt, README's command tour) present as THE way to sign in from SSH or a container. ChooseDiscoveryProvider returned null for any non-interactive session, so those documented invocations exited before starting the device flow. `--device` is an explicit request in the same class as `--github`, not the implicit fallback this branch removed, and only GitHub has a device flow — so it selects the legacy provider on the headless branch only. Deliberately not folded into the `--github` check at the top: on a machine with a browser, `--device` has always meant "use the device flow if the login step needs GitHub", never "take discovery off org SSO", and routing it there would regress the interactive path in the opposite direction. Both directions are now pinned by tests. Docs updated to name the headless escape hatches (--device / --github / --server-url) rather than implying discovery simply stops, per the Qodo review's valid half. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks both — one real regression caught, one doc gap, one claim that doesn't hold. Addressed in c870be5. ✅ Codex:
|
Closes #534. Follow-on work tracked in #535. Linear auto-imports both.
Problem
A non-interactive
kcap setup/kcap loginwith no--server-urlfell back to the legacy GitHub App provider:The fallback existed for a real reason — GitHub has a device flow, while WorkOS authenticates through a
http://127.0.0.1:{port}/callbackloopback that a browser on another machine can never reach. But it dead-ended: the GitHub App branch has no provisioning path by design (it discovers orgs you already belong to), so a headless user with no workspace completed a full device-code sign-in and was then told:For someone evaluating kcap there is no admin. Telemetry caught one such user on 2026-08-11 who hit it, retried the entire flow, hit it again 4 minutes later, and gave up.
cli_setup_workspace_offerednever fired — they were never asked, they didn't decline.Why remove the routing rather than fix the copy
CLI telemetry since the funnel went live (released builds, non-CI):
Every interactive user already lands on WorkOS. Headless routing was the only remaining source of new GitHub App sign-ins. Better copy on that dead-end would have advertised a provider we're migrating off and polished the one path still feeding it.
Change
ChooseDiscoveryProviderreturnsnullfor a non-interactive session. Both call sites reportHeadlessDiscoveryUnsupportedMessage()and stop:No GitHub App mention; both routes that actually work are named. The signup URL derives from
ProvisioningEndpoint.Url, so it honours the existingKCAP_SIGNUP_URLdev/preview override.The check also moves ahead of the auth-service call in both call sites, so the run fails in ~2s instead of after a full device-code dance it can't use.
Blast radius — deliberately narrow
--server-url).--server-urlthe provider comes from that server's/auth/config(SetupCommand.cs:892).--githubremains an explicit escape hatch and still works headless — verified against the real binary, it proceeds to the device flow.Tests
TDD: tests written first, watched fail, then implemented.
Headless_discovery_does_not_fall_back_to_legacy_github_appExplicit_github_flag_still_selects_legacy_provider_when_headlessHeadless_discovery_message_offers_signup_and_server_url_but_never_the_github_app— asserts the copy contains noGitHub App, and does contain/signupand--server-urlChooseDiscoveryProvider_honors_flags_and_defaultupdated (it pinned the old fallback)Verified against the AOT binary with
SSH_CONNECTIONset:kcap setupheadlesskcap loginheadlesskcap setup --githubheadlessFull unit suite: 56 failures here vs 57 on a clean
origin/mainbaseline, all the known load-sensitive timing flakes. The two that differed run-to-run (Agent_finalized_before_the_bind_completes…,Wedged_initialize_reaches_LaunchFailed…) both pass in isolation and touch neither auth nor setup. AOT publish clean — no IL2026/IL3050.Docs
README.mdandhelp-login.txtboth asserted the old fallback ("discovery falls back to GitHub Device Flow"); both now describe the interactive requirement and the two routes.🤖 Generated with Claude Code