FN-7624: fix onboarding GitHub sign-in erroring with model-not-found

Fixes the onboarding/settings GitHub step so it never offers a dashboard OAuth login for github (no github OAuth provider is ever registered; pi only ships anthropic/github-copilot/openai-codex), replacing the broken Connect OAuth button with gh CLI guidance and a clearer server-side error.

- Remove the "Connect OAuth (optional)" button and its login-instructions panel from the onboarding branch that runs when hasGithubProvider is false (ModelOnboardingModal.tsx), since it always called handleLogin("github") against a non-existent provider
- Update ModelOnboardingModal tests to cover the new gh-CLI-only flow
- Make POST /api/auth/login return a clear, actionable 400 naming the requested provider, the registered dashboard OAuth providers, and that GitHub integration uses gh CLI/token auth instead of a generic "Unknown provider" / model-not-found error
- Add routes-auth.test.ts coverage for the improved unknown-provider error message
- Add a patch changeset documenting the fix

Files changed:
 .changeset/fn-7624-github-onboarding-auth.md       |  7 +++++
 packages/dashboard/app/components/ModelOnboardingModal.tsx | 32 +++++++---------------
 packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx | 32 +++++++++++++++++++++-
 packages/dashboard/src/__tests__/routes-auth.test.ts | 22 +++++++++++++++
 packages/dashboard/src/routes/register-auth-routes.ts | 14 +++++++++-
 5 files changed, 83 insertions(+), 24 deletions(-)

Fusion-Task-Id: FN-7624
Fusion-Task-Lineage: e7be1b0a-6e50-479d-b56e-1284e8a94b7f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-07 08:02:04 -07:00
parent ebf8f87fdb
commit a4f5fbc0e9
5 changed files with 83 additions and 24 deletions

View File

@@ -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".

View File

@@ -2882,6 +2882,16 @@ export function ModelOnboardingModal({
</div>
)}
{/*
* 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 ? (
<div className="model-onboarding-github-optional">
<div className="optional-icon optional-icon--github" aria-hidden="true">
@@ -2913,29 +2923,7 @@ export function ModelOnboardingModal({
? t("setup.continueWithGhCli", "Continue with gh CLI auth →")
: t("setup.continueWithoutGitHub", "Continue without GitHub →")}
</button>
{isGitHubReadyViaCli && (
(authActionInProgress === "github" || isGithubLoginInProgress) ? (
<button className="btn btn-sm" disabled>
<Loader2 size={14} className="onboarding-spinner" />
{t("setup.waitingForOauthLogin", "Waiting for OAuth login…")}
</button>
) : (
<button
className="btn btn-sm"
onClick={() => handleLogin("github")}
>
<ProviderIcon provider="github" size="sm" />
{t("setup.connectOauthOptional", "Connect OAuth (optional)")}
</button>
)
)}
</div>
{isGitHubReadyViaCli && (authActionInProgress === "github" || isGithubLoginInProgress) && loginInstructions.github && (
<LoginInstructions
instructions={loginInstructions.github}
data-testid="onboarding-login-instructions-github"
/>
)}
</div>
) : (
<>

View File

@@ -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(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} projectId="proj_123" />);
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 () => {

View File

@@ -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<typeof vi.fn>).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<typeof vi.fn>).mockImplementation(

View File

@@ -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;