diff --git a/.changeset/fn-7624-github-onboarding-auth.md b/.changeset/fn-7624-github-onboarding-auth.md new file mode 100644 index 0000000000..79afefa7e9 --- /dev/null +++ b/.changeset/fn-7624-github-onboarding-auth.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix onboarding GitHub sign-in button erroring instead of starting GitHub auth. +category: fix +dev: The onboarding/settings GitHub step no longer offers dashboard-managed OAuth login when no `github` OAuth provider is registered (pi ships only anthropic/github-copilot/openai-codex); it now presents gh CLI (`gh auth login`) guidance. `/api/auth/login` returns a clear unknown-provider error for `github` instead of a misleading "model not found". diff --git a/packages/dashboard/app/components/ModelOnboardingModal.tsx b/packages/dashboard/app/components/ModelOnboardingModal.tsx index 8cffb4953b..d3b92f0316 100644 --- a/packages/dashboard/app/components/ModelOnboardingModal.tsx +++ b/packages/dashboard/app/components/ModelOnboardingModal.tsx @@ -2882,6 +2882,16 @@ export function ModelOnboardingModal({ )} + {/* + * FNXC:ProviderAuth 2026-07-07-00:00: + * FN-7624: no `github` OAuth provider is ever registered on any Fusion host (pi ships only + * anthropic/github-copilot/openai-codex), so this whole `!hasGithubProvider` branch must never + * render a dashboard OAuth login affordance (a previous "Connect OAuth (optional)" button here + * called handleLogin("github") unconditionally when the gh CLI was ready, which always failed + * against a non-existent provider). The gh-CLI-ready and not-ready copy below are the only + * offered actions; OAuth login only appears in the `hasGithubProvider` branch below, which is + * unreachable while no `github` provider is registered. + */} {!hasGithubProvider ? (
- {isGitHubReadyViaCli && (authActionInProgress === "github" || isGithubLoginInProgress) && loginInstructions.github && ( - - )}
) : ( <> diff --git a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx index 404b4f53e0..ebe9e80fec 100644 --- a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx @@ -1906,7 +1906,37 @@ describe("ModelOnboardingModal", () => { expect(screen.getByTestId("onboarding-git-prerequisite")).toHaveTextContent("Install Git before project setup"); expect(screen.getByRole("button", { name: "Continue with gh CLI auth →" })).toBeTruthy(); - expect(screen.getByRole("button", { name: /Connect OAuth/ })).toBeTruthy(); + // FN-7624: no `github` OAuth provider is registered here (providers: []), so no dashboard + // OAuth login affordance may render — clicking it used to call handleLogin("github") against + // a provider that does not exist, surfacing a login error. Only the gh-CLI continue path is offered. + expect(screen.queryByRole("button", { name: /Connect OAuth/ })).toBeNull(); + }); + + it("FN-7624: never renders a dashboard GitHub OAuth login affordance when no github OAuth provider is registered, across all gh CLI states", async () => { + const scenarios: Array<{ label: string; ghCli?: { available: boolean; authenticated: boolean } }> = [ + { label: "gh CLI missing", ghCli: { available: false, authenticated: false } }, + { label: "gh CLI unauthenticated", ghCli: { available: true, authenticated: false } }, + { label: "gh CLI authenticated", ghCli: { available: true, authenticated: true } }, + ]; + + for (const scenario of scenarios) { + mockFetchAuthStatus.mockResolvedValueOnce({ + providers: [], + ghCli: scenario.ghCli, + }); + + const { unmount } = render(); + + await navigateToGitHubStep(); + + // No OAuth login trigger of any kind should render — no "Connect GitHub OAuth", no + // "Connect OAuth (optional)", no leftover empty CTA wrapper — since providerAvailable is false. + expect(screen.queryByRole("button", { name: /Connect OAuth/ })).toBeNull(); + expect(screen.queryByRole("button", { name: /Connect GitHub OAuth/ })).toBeNull(); + expect(screen.queryByTestId("onboarding-github-connect-cta")).toBeNull(); + + unmount(); + } }); it("does not show a Git prerequisite shell when auth status fails to load", async () => { diff --git a/packages/dashboard/src/__tests__/routes-auth.test.ts b/packages/dashboard/src/__tests__/routes-auth.test.ts index 52f8a86e62..5e44a35166 100644 --- a/packages/dashboard/src/__tests__/routes-auth.test.ts +++ b/packages/dashboard/src/__tests__/routes-auth.test.ts @@ -2457,6 +2457,28 @@ describe("POST /auth/login", () => { expect(res.body.error).toContain("Unknown provider"); }); + it("FN-7624: returns a clear, non-misleading 400 for provider: \"github\" when only built-in OAuth providers are registered (never a 'model not found' style error)", async () => { + // Only the built-in dashboard OAuth providers are registered — no `github` provider exists, + // matching the real pi OAuth registry (anthropic / github-copilot / openai-codex only). + (authStorage.getOAuthProviders as ReturnType).mockReturnValue([ + { id: "anthropic", name: "Anthropic" }, + { id: "github-copilot", name: "GitHub Copilot" }, + { id: "openai-codex", name: "OpenAI Codex" }, + ]); + + // This reproduces the original symptom: the onboarding GitHub button used to call + // POST /api/auth/login { provider: "github" } and surface a confusing error. + const res = await REQUEST(buildApp(), "POST", "/api/auth/login", JSON.stringify({ provider: "github" }), { + "Content-Type": "application/json", + }); + + expect(res.status).toBe(400); + expect(res.body.error).toContain("Unknown provider"); + expect(res.body.error).toContain("github"); + // Assert the error is never the misleading "model not found" style message the user reported. + expect(res.body.error.toLowerCase()).not.toContain("model not found"); + }); + it("returns 409 when login is already in progress for the same provider", async () => { let releaseLogin: (() => void) | undefined; (authStorage.login as ReturnType).mockImplementation( diff --git a/packages/dashboard/src/routes/register-auth-routes.ts b/packages/dashboard/src/routes/register-auth-routes.ts index cf38503980..f5402fce53 100644 --- a/packages/dashboard/src/routes/register-auth-routes.ts +++ b/packages/dashboard/src/routes/register-auth-routes.ts @@ -1149,7 +1149,19 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => { const oauthProviders = storage.getOAuthProviders(); const found = oauthProviders.find((p) => p.id === provider || p.id === storageProvider); if (!found) { - throw badRequest(`Unknown provider: ${provider}`); + /* + * FNXC:ProviderAuth 2026-07-07-00:00: + * FN-7624: `github` is NOT a dashboard-managed OAuth provider — pi's OAuth registry only + * ships `anthropic`, `github-copilot`, and `openai-codex` (see @earendil-works/pi-ai/oauth via + * packages/engine/src/auth-storage.ts). Fusion's real GitHub integration is gh CLI / token + * based (`githubAuthMode: "gh-cli" | "token"`), so the onboarding/settings UI must never offer + * a dashboard OAuth login for `github`. If this branch is ever reached for `github` anyway + * (e.g. a stale client build), return a clear, actionable message naming the provider and that + * no OAuth flow exists for it — never a generic/misleading error like "model not found". + */ + throw badRequest( + `Unknown provider: "${provider}" has no registered OAuth login flow. Registered dashboard OAuth providers are: ${oauthProviders.map((p) => p.id).join(", ") || "none"}. GitHub integration uses the GitHub CLI (run \`gh auth login\`) or a token, not dashboard OAuth.`, + ); } const loginProvider = found.id === provider ? provider : storageProvider;