diff --git a/packages/dashboard/src/__tests__/routes-auth.test.ts b/packages/dashboard/src/__tests__/routes-auth.test.ts index b69ae4c4aa..df21e6d5d6 100644 --- a/packages/dashboard/src/__tests__/routes-auth.test.ts +++ b/packages/dashboard/src/__tests__/routes-auth.test.ts @@ -2188,7 +2188,10 @@ describe("POST /auth/login", () => { expect(observedPromptInput).toBe("manual-code"); }); - it("normalizes Anthropic subscription pasted callback URLs with fragment parameters", async () => { + it.each([ + ["fragment", "http://localhost:53692/callback#code=fragment-code&state=expected-state", "code=fragment-code&state=expected-state"], + ["schemeless", "localhost:53692/callback?code=schemeless-code&state=expected-state", "code=schemeless-code&state=expected-state"], + ])("normalizes Anthropic subscription pasted callback URLs with %s parameters", async (_case, callbackUrl, expectedInput) => { (authStorage.getOAuthProviders as ReturnType).mockReturnValue([{ id: "anthropic", name: "Anthropic" }]); let observedManualInput: string | undefined; @@ -2203,7 +2206,6 @@ describe("POST /auth/login", () => { }); await new Promise((resolve) => setTimeout(resolve, 0)); - const callbackUrl = "http://localhost:53692/callback#code=fragment-code&state=expected-state"; const submitRes = await REQUEST(app, "POST", "/api/auth/manual-code", JSON.stringify({ provider: "anthropic-subscription", code: callbackUrl }), { "Content-Type": "application/json", }); @@ -2211,7 +2213,7 @@ describe("POST /auth/login", () => { await loginReq; await new Promise((resolve) => setTimeout(resolve, 0)); - expect(observedManualInput).toBe("code=fragment-code&state=expected-state"); + expect(observedManualInput).toBe(expectedInput); }); it("prefers browser login for openai-codex multi-option prompts", async () => { diff --git a/packages/dashboard/src/routes/register-auth-routes.ts b/packages/dashboard/src/routes/register-auth-routes.ts index 7e7b10f924..e6dc418319 100644 --- a/packages/dashboard/src/routes/register-auth-routes.ts +++ b/packages/dashboard/src/routes/register-auth-routes.ts @@ -280,8 +280,8 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => { /* FNXC:ProviderAuth 2026-07-04-00:00: - Anthropic subscription and Codex pasted-login flows must accept the exact browser address bar after redirect, including providers/browsers that place OAuth `code` and `state` in the URL fragment. - The upstream CLI parser treats a syntactically valid URL as search-only, so normalize fragment callbacks to query-param text before resolving the pending manual-code prompt. + Anthropic subscription and Codex pasted-login flows must accept the exact browser address bar after redirect, including providers/browsers that place OAuth `code` and `state` in the URL fragment or omit the URL scheme. + The upstream CLI parser treats a syntactically valid URL as search-only and schemeless localhost text as raw parameters, so normalize callback inputs to query-param text before resolving the pending manual-code prompt. */ const normalized = new URLSearchParams(); normalized.set("code", hashCode);