فهرست منبع

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
ClineXDiego 2 ماه پیش
والد
کامیت
70a9904

+ 5 - 0
.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

+ 7 - 0
.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`.

+ 15 - 2
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,

+ 7 - 2
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<String> {
+	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` }
 }

+ 20 - 4
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<string> {
 }
 
 /**
- * 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<void> {
 	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}`,
+			})
+		}
+	}
 }