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:
7
.changeset/fn-7624-github-onboarding-auth.md
Normal file
7
.changeset/fn-7624-github-onboarding-auth.md
Normal 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".
|
||||
@@ -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>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user