From 70a99047ed3a2b26ab92d7056ebe71ca2226110f Mon Sep 17 00:00:00 2001 From: ClineXDiego Date: Sat, 7 Feb 2026 15:25:14 -0300 Subject: [PATCH] fix: use vscode.env.asExternalUri for auth callback URLs in VS Code Web (#9144) * fix: use vscode.env.asExternalUri for auth callback URLs in VS Code Web The OAuth callback redirect was broken in VS Code Web (code serve-web) environments because the callback URL used a raw vscode:// URI scheme, which the OS would route to the local desktop VS Code app instead of the web instance. This change wraps both getCallbackUrl() and getIdeRedirectUri() with vscode.env.asExternalUri() which properly transforms URIs based on the environment: - Desktop VS Code: unchanged (vscode://...) - VS Code Remote SSH: adds remote authority for proper routing - VS Code Web: transforms to HTTPS URL that routes through the web server Fixes #5109 (remaining callback redirect issue) Related: #2152 * fix: use HTTP-based auth callback for VS Code Web mode In VS Code Web (code serve-web), vscode:// URIs redirect to the desktop app instead of staying in the browser. This change uses AuthHandler (local HTTP server) for the auth callback in web mode, matching how CLI/standalone already handles auth. - getCallbackUrl: use AuthHandler when UIKind.Web - getIdeRedirectUri: return empty in web mode to avoid vscode:// redirect * fix: add fallback for openExternal RPC for JetBrains compatibility The openExternal host bridge RPC is not implemented in the JetBrains plugin, causing sign-in to fail silently. This adds a fallback to the 'open' npm package when the host RPC fails with UNIMPLEMENTED. Fixes #9164, #9137, #9138 --- .../fix-jetbrains-open-external-fallback.md | 5 ++++ .changeset/fix-vscode-web-auth-callback.md | 7 ++++++ src/extension.ts | 17 +++++++++++-- .../hostbridge/env/getIdeRedirectUri.ts | 9 +++++-- src/utils/env.ts | 24 +++++++++++++++---- 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 .changeset/fix-jetbrains-open-external-fallback.md create mode 100644 .changeset/fix-vscode-web-auth-callback.md diff --git a/.changeset/fix-jetbrains-open-external-fallback.md b/.changeset/fix-jetbrains-open-external-fallback.md new file mode 100644 index 0000000000..2a5513e7f5 --- /dev/null +++ b/.changeset/fix-jetbrains-open-external-fallback.md @@ -0,0 +1,5 @@ +--- +"claude-dev": patch +--- + +Fix JetBrains sign-in regression by adding fallback for openExternal RPC diff --git a/.changeset/fix-vscode-web-auth-callback.md b/.changeset/fix-vscode-web-auth-callback.md new file mode 100644 index 0000000000..fce2e95b2b --- /dev/null +++ b/.changeset/fix-vscode-web-auth-callback.md @@ -0,0 +1,7 @@ +--- +"cline": patch +--- + +fix: use vscode.env.asExternalUri for auth callback URLs only in VS Code Web + +Fixes OAuth callback redirect in VS Code Web (`code serve-web`) environments by using `vscode.env.asExternalUri()` to resolve the callback URI. This is gated behind a `vscode.env.uiKind === UIKind.Web` check so regular desktop VS Code continues to use the `vscode://` URI directly, avoiding unintended transformations from `asExternalUri`. diff --git a/src/extension.ts b/src/extension.ts index b569cb4307..9142131084 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -352,7 +352,7 @@ export async function activate(context: vscode.ExtensionContext) { ) context.subscriptions.push( - vscode.commands.registerCommand(commands.FocusChatInput, async (preserveEditorFocus: boolean = false) => { + vscode.commands.registerCommand(commands.FocusChatInput, async (preserveEditorFocus = false) => { const webview = WebviewProvider.getInstance() as VscodeWebviewProvider // Show the webview @@ -581,7 +581,20 @@ function setupHostProvider(context: ExtensionContext) { const createCommentReview = () => getVscodeCommentReviewController() const createTerminalManager = () => new VscodeTerminalManager() - const getCallbackUrl = async () => `${vscode.env.uriScheme || "vscode"}://${context.extension.id}` + const getCallbackUrl = async () => { + if (vscode.env.uiKind === vscode.UIKind.Web) { + // In VS Code Web (code serve-web), vscode:// URIs redirect to the desktop app + // instead of staying in the browser. Use an HTTP-based callback server instead, + // which the browser can navigate to directly after auth completes. + const { AuthHandler } = await import("@/hosts/external/AuthHandler") + const authHandler = AuthHandler.getInstance() + authHandler.setEnabled(true) + return authHandler.getCallbackUrl() + } + // In regular desktop VS Code, use the vscode:// URI protocol handler directly. + const baseUri = vscode.Uri.parse(`${vscode.env.uriScheme || "vscode"}://${context.extension.id}`) + return baseUri.toString(true) + } HostProvider.initialize( createWebview, createDiffView, diff --git a/src/hosts/vscode/hostbridge/env/getIdeRedirectUri.ts b/src/hosts/vscode/hostbridge/env/getIdeRedirectUri.ts index 5404e1ab3d..3a573cb061 100644 --- a/src/hosts/vscode/hostbridge/env/getIdeRedirectUri.ts +++ b/src/hosts/vscode/hostbridge/env/getIdeRedirectUri.ts @@ -2,7 +2,12 @@ import { EmptyRequest, String } from "@shared/proto/cline/common" import * as vscode from "vscode" export async function getIdeRedirectUri(_: EmptyRequest): Promise { + if (vscode.env.uiKind === vscode.UIKind.Web) { + // In VS Code Web (code serve-web), the auth callback is handled by an HTTP server + // (AuthHandler). Returning empty here means the success page won't try to redirect + // to a vscode:// URI (which would open the desktop app instead of the web tab). + return { value: "" } + } const uriScheme = vscode.env.uriScheme || "vscode" - const url = `${uriScheme}://saoudrizwan.claude-dev` - return { value: url } + return { value: `${uriScheme}://saoudrizwan.claude-dev` } } diff --git a/src/utils/env.ts b/src/utils/env.ts index 682b77de48..bc79f6f168 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -1,4 +1,5 @@ import { EmptyRequest, StringRequest } from "@shared/proto/cline/common" +import { ShowMessageType } from "@shared/proto/host/window" import { HostProvider } from "@/hosts/host-provider" import { Logger } from "@/shared/services/Logger" @@ -33,13 +34,28 @@ export async function readTextFromClipboard(): Promise { } /** - * Opens an external URL in the default browser + * Opens an external URL in the default browser. + * Uses the host bridge RPC first (VS Code's openExternal which handles remote environments). + * Falls back to the `open` npm package if the host doesn't implement the RPC (e.g., JetBrains). * @param url The URL to open * @returns Promise that resolves when the operation is complete - * @throws Error if the operation fails */ export async function openExternal(url: string): Promise { Logger.log("Opening browser:", url) - // Use VS Code's openExternal which handles remote environments - await HostProvider.env.openExternal(StringRequest.create({ value: url })) + try { + await HostProvider.env.openExternal(StringRequest.create({ value: url })) + } catch (error) { + // Fallback for hosts that don't implement openExternal (e.g., JetBrains plugin) + Logger.warn(`Host openExternal RPC failed, falling back to 'open' package: ${error}`) + try { + const open = (await import("open")).default + await open(url) + } catch (fallbackError) { + Logger.error(`Fallback 'open' also failed: ${fallbackError}`) + HostProvider.window.showMessage({ + type: ShowMessageType.ERROR, + message: `Failed to open URL: ${url}`, + }) + } + } }