From 0efe4c6983f661a63ec0833a5dc0706a1d83d369 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 27 Oct 2026 12:11:25 +0000 Subject: [PATCH 1/4] test(provider-cursor): reproduce #25926, Cursor sign-in hides the SDK's failure reason Co-Authored-By: Claude Opus 5.5 --- packages/provider-cursor/src/server/auth.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/provider-cursor/src/server/auth.test.ts b/packages/provider-cursor/src/server/auth.test.ts index 76ff39678a831..bf796e7f73237 100644 --- a/packages/provider-cursor/src/server/auth.test.ts +++ b/packages/provider-cursor/src/server/auth.test.ts @@ -11,7 +11,7 @@ import * as Option from "effect/Option"; import * as Stream from "effect/Stream"; import { makeCursorAuth, type CursorAuthOptions } from "./auth.ts"; -import { InMemoryCredentialStore } from "./sdk.ts"; +import { AuthenticationError, InMemoryCredentialStore } from "./sdk.ts"; const instanceId = ProviderInstanceId.make("cursor-personal"); const owner = "owner-client"; @@ -215,6 +215,20 @@ it.layer(NodeServices.layer)("CursorAuth", (it) => { }).pipe(Effect.scoped), ); + it.effect("shows the Cursor SDK's reason when browser login fails", () => + Effect.gen(function* () { + const sdkMessage = + "Login succeeded, but creating an SDK API key failed: [permission_denied] user API keys are disabled for this team (your team's settings may restrict user API keys — ask a team admin, or pass an existing API key directly)"; + const harness = yield* makeHarness({ + login: () => Promise.reject(new AuthenticationError(sdkMessage)), + }); + yield* harness.auth.controller.start(owner); + const failed = yield* phase(harness.auth, "failed"); + expect(failed.message).toContain(sdkMessage); + expect(yield* harness.auth.readApiKey).toBeUndefined(); + }).pipe(Effect.scoped), + ); + it.effect("clears only this instance's credential after its sessions stop", () => Effect.gen(function* () { const harness = yield* makeHarness(); From 537d729460fd2f2cd23b10afc39cc3a863e73ba0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 27 Oct 2026 12:15:42 +0000 Subject: [PATCH 2/4] fix(provider-cursor): Cursor sign-in shows the SDK's failure reason and logs the cause Fixes pingdotgg/t3code#25926 Co-Authored-By: Claude Opus 5.5 --- packages/provider-cursor/src/server/auth.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/provider-cursor/src/server/auth.ts b/packages/provider-cursor/src/server/auth.ts index 0bcd3c0de9034..e16e985a9f8f5 100644 --- a/packages/provider-cursor/src/server/auth.ts +++ b/packages/provider-cursor/src/server/auth.ts @@ -18,7 +18,7 @@ import * as Semaphore from "effect/Semaphore"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; -import { Cursor, InMemoryCredentialStore } from "./sdk.ts"; +import { Cursor, CursorSdkError, InMemoryCredentialStore } from "./sdk.ts"; import type { ProviderAuthController } from "@t3tools/provider-core/server/auth"; const AUTH_TIMEOUT_MS = 300_000; @@ -194,7 +194,10 @@ export const makeCursorAuth = Effect.fn("makeCursorAuth")(function* (options: Cu new ProviderSetupError({ instanceId: options.instanceId, operation: "start", - detail: "Cursor sign-in failed. Start sign-in again.", + detail: + cause instanceof CursorSdkError + ? cause.message + : "Cursor sign-in failed. Start sign-in again.", cause, }), }); @@ -260,6 +263,10 @@ export const makeCursorAuth = Effect.fn("makeCursorAuth")(function* (options: Cu }), ), }), + Effect.tapCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => Effect.logWarning("Cursor sign-in failed", cause), + ), Effect.exit, Effect.flatMap((result) => lock.withPermits(1)( From 13aa34182262a316594e1e5a37e97728b7456808 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 27 Oct 2026 12:23:01 +0000 Subject: [PATCH 3/4] fix(provider-cursor): pin that failed Cursor sign-in logs its cause and cancel does not Drops the interrupt filter: cancellation interrupts the login fiber, so the cause tap never runs for it. Co-Authored-By: Claude Opus 5.5 --- .../provider-cursor/src/server/auth.test.ts | 30 ++++++++++++++----- packages/provider-cursor/src/server/auth.ts | 5 +--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/provider-cursor/src/server/auth.test.ts b/packages/provider-cursor/src/server/auth.test.ts index bf796e7f73237..0e169b1bdce1f 100644 --- a/packages/provider-cursor/src/server/auth.test.ts +++ b/packages/provider-cursor/src/server/auth.test.ts @@ -2,11 +2,13 @@ import type { SdkLoginOptions, StoredSdkCredentials } from "@cursor/sdk"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { ProviderInstanceId, ProviderSetupError, type ProviderAuthState } from "@t3tools/contracts"; +import * as Cause from "effect/Cause"; import * as Clock from "effect/Clock"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; +import * as Logger from "effect/Logger"; import * as Option from "effect/Option"; import * as Stream from "effect/Stream"; @@ -69,6 +71,13 @@ const makeHarness = Effect.fn("makeCursorAuthHarness")(function* ( }; }); +const captureWarnings = (warnings: string[]) => + Logger.layer([ + Logger.make(({ logLevel, cause }) => { + if (logLevel === "Warn") warnings.push(Cause.pretty(cause)); + }), + ]); + type Auth = Effect.Success>; const phase = (auth: Auth, phase: ProviderAuthState["phase"], session = owner) => auth.controller.subscribe(session).pipe( @@ -167,8 +176,9 @@ it.layer(NodeServices.layer)("CursorAuth", (it) => { }).pipe(Effect.scoped), ); - it.effect("aborts cancellation and ignores a late successful SDK login", () => - Effect.gen(function* () { + it.effect("aborts cancellation and ignores a late successful SDK login", () => { + const warnings: string[] = []; + return Effect.gen(function* () { const harness = yield* makeHarness(); yield* harness.auth.controller.start(owner); const waiting = yield* phase(harness.auth, "waiting"); @@ -180,8 +190,9 @@ it.layer(NodeServices.layer)("CursorAuth", (it) => { yield* Effect.promise(() => harness.returned.promise); expect(yield* harness.auth.readApiKey).toBeUndefined(); expect(harness.changes).toEqual([]); - }).pipe(Effect.scoped), - ); + expect(warnings).toEqual([]); + }).pipe(Effect.scoped, Effect.provide(captureWarnings(warnings))); + }); it.effect("discards a credential rejected by verification and permits a fresh login", () => Effect.gen(function* () { @@ -215,8 +226,9 @@ it.layer(NodeServices.layer)("CursorAuth", (it) => { }).pipe(Effect.scoped), ); - it.effect("shows the Cursor SDK's reason when browser login fails", () => - Effect.gen(function* () { + it.effect("shows the Cursor SDK's reason when browser login fails and logs its cause", () => { + const warnings: string[] = []; + return Effect.gen(function* () { const sdkMessage = "Login succeeded, but creating an SDK API key failed: [permission_denied] user API keys are disabled for this team (your team's settings may restrict user API keys — ask a team admin, or pass an existing API key directly)"; const harness = yield* makeHarness({ @@ -226,8 +238,10 @@ it.layer(NodeServices.layer)("CursorAuth", (it) => { const failed = yield* phase(harness.auth, "failed"); expect(failed.message).toContain(sdkMessage); expect(yield* harness.auth.readApiKey).toBeUndefined(); - }).pipe(Effect.scoped), - ); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain(sdkMessage); + }).pipe(Effect.scoped, Effect.provide(captureWarnings(warnings))); + }); it.effect("clears only this instance's credential after its sessions stop", () => Effect.gen(function* () { diff --git a/packages/provider-cursor/src/server/auth.ts b/packages/provider-cursor/src/server/auth.ts index e16e985a9f8f5..408fd64e1ff26 100644 --- a/packages/provider-cursor/src/server/auth.ts +++ b/packages/provider-cursor/src/server/auth.ts @@ -263,10 +263,7 @@ export const makeCursorAuth = Effect.fn("makeCursorAuth")(function* (options: Cu }), ), }), - Effect.tapCauseIf( - (cause) => !Cause.hasInterruptsOnly(cause), - (cause) => Effect.logWarning("Cursor sign-in failed", cause), - ), + Effect.tapCause((cause) => Effect.logWarning("Cursor sign-in failed", cause)), Effect.exit, Effect.flatMap((result) => lock.withPermits(1)( From 91334658a7ae4d2a82783b799844eeaaa626d6e3 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 09:44:41 +0000 Subject: [PATCH 4/4] fix(provider-cursor): map Cursor sign-in failures to fixed reasons The sign-in error detail copied the SDK's message, which embeds backend text. Classify the SDK's known login failures (API key creation refused, login failed or timed out) into fixed messages and keep the SDK error in the cause and the warning log. AI-assisted; verified with the tests and tools listed above. --- .../provider-cursor/src/server/auth.test.ts | 5 +++-- packages/provider-cursor/src/server/auth.ts | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/provider-cursor/src/server/auth.test.ts b/packages/provider-cursor/src/server/auth.test.ts index c4929213a6fef..d4ad401b69b51 100644 --- a/packages/provider-cursor/src/server/auth.test.ts +++ b/packages/provider-cursor/src/server/auth.test.ts @@ -230,7 +230,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("CursorAuth", (it }).pipe(Effect.scoped), ); - it.effect("shows the Cursor SDK's reason when browser login fails and logs its cause", () => { + it.effect("shows a fixed reason when browser login fails and logs the SDK cause", () => { const warnings: string[] = []; return Effect.gen(function* () { const sdkMessage = @@ -240,7 +240,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("CursorAuth", (it }); yield* harness.auth.controller.start(owner); const failed = yield* phase(harness.auth, "failed"); - expect(failed.message).toContain(sdkMessage); + expect(failed.message).toContain("could not create an API key"); + expect(failed.message).not.toContain("permission_denied"); expect(yield* harness.auth.readApiKey).toBeUndefined(); expect(warnings).toHaveLength(1); expect(warnings[0]).toContain(sdkMessage); diff --git a/packages/provider-cursor/src/server/auth.ts b/packages/provider-cursor/src/server/auth.ts index 009f4a829daf0..c04c703dfefa6 100644 --- a/packages/provider-cursor/src/server/auth.ts +++ b/packages/provider-cursor/src/server/auth.ts @@ -23,6 +23,19 @@ import type { ProviderAuthController } from "@t3tools/provider-core/server/auth" const AUTH_TIMEOUT_MS = 300_000; +// The SDK has no code distinguishing these failures, so classify by its fixed message prefixes. +const loginFailureDetail = (cause: unknown) => { + if (isCursorSdkError(cause, "CursorSdkError")) { + if (cause.message.startsWith("Login succeeded, but creating an SDK API key failed")) { + return "Cursor signed you in but could not create an API key. Your team's settings may restrict API keys: ask a team admin, or add CURSOR_API_KEY in provider settings."; + } + if (cause.message.startsWith("Login failed or timed out")) { + return "Cursor sign-in failed or timed out. Check that this machine can reach Cursor, then start sign-in again."; + } + } + return "Cursor sign-in failed. Start sign-in again."; +}; + interface AuthFlow { readonly id: string; readonly owner: string; @@ -204,9 +217,7 @@ export const makeCursorAuth = Effect.fn("makeCursorAuth")(function* (options: Cu new ProviderSetupError({ instanceId: options.instanceId, operation: "start", - detail: isCursorSdkError(cause, "CursorSdkError") - ? cause.message - : "Cursor sign-in failed. Start sign-in again.", + detail: loginFailureDetail(cause), cause, }), });