Browse Source

fix: use vscode.env.openExternal for auth in remote environments (#9111)

* fix: use vscode.env.openExternal for auth in remote environments

Fixes #5109

The OAuth authentication flow was broken in VS Code Server and remote
environments because the code used the npm 'open' package directly, which
tries to launch a browser on the server itself (which has no display).

This change routes browser URL opening through VS Code's native
vscode.env.openExternal() API via the HostBridge pattern, which properly
forwards URLs to the user's local machine in remote environments.

Changes:
- Added openExternal RPC to proto/host/env.proto
- Created VS Code handler using vscode.env.openExternal()
- Updated src/utils/env.ts to use HostProvider.env.openExternal()
- Added openExternal to CLI CliEnvServiceClient (uses npm 'open')
- Added openExternal to CLI ACPEnvServiceClient (uses npm 'open')

Related issues: #5394, #2152, #7971

* chore: add changeset for vscode server auth fix

* refactor: extract shared openUrlInBrowser utility for CLI
ClineXDiego 2 months ago
parent
commit
f440f3a

+ 7 - 0
.changeset/fix-vscode-server-auth.md

@@ -0,0 +1,7 @@
+---
+"cline": patch
+---
+
+fix: use vscode.env.openExternal for auth in remote environments
+
+Fixes OAuth authentication in VS Code Server and remote environments by routing browser URL opening through VS Code's native openExternal API instead of the npm 'open' package.

+ 10 - 0
cli/src/acp/ACPHostBridgeClientProvider.ts

@@ -172,6 +172,16 @@ class ACPEnvServiceClient implements EnvServiceClientInterface {
 		Logger.debug("[ACPEnvServiceClient] shutdown called (stub)")
 		return proto.cline.Empty.create()
 	}
+
+	async openExternal(request: proto.cline.StringRequest): Promise<proto.cline.Empty> {
+		const url = request.value || ""
+		if (url) {
+			Logger.debug(`[ACPEnvServiceClient] openExternal: ${url}`)
+			const { openUrlInBrowser } = await import("../utils/browser")
+			await openUrlInBrowser(url)
+		}
+		return proto.cline.Empty.create()
+	}
 }
 
 /**

+ 11 - 0
cli/src/controllers/index.ts

@@ -142,6 +142,17 @@ export class CliEnvServiceClient implements EnvServiceClientInterface {
 		printInfo("Shutting down...")
 		return proto.cline.Empty.create()
 	}
+
+	async openExternal(request: proto.cline.StringRequest): Promise<proto.cline.Empty> {
+		const url = request.value || ""
+		if (url) {
+			printInfo(`🌐 Opening: ${url}`)
+			// Dynamically import 'open' to open URL in default browser
+			const { default: open } = await import("open")
+			await open(url)
+		}
+		return proto.cline.Empty.create()
+	}
 }
 
 /**

+ 10 - 0
cli/src/utils/browser.ts

@@ -0,0 +1,10 @@
+/**
+ * Opens a URL in the user's default browser.
+ * Uses dynamic import of the 'open' package to open URLs.
+ *
+ * @param url - The URL to open in the browser
+ */
+export async function openUrlInBrowser(url: string): Promise<void> {
+	const { default: open } = await import("open")
+	await open(url)
+}

+ 5 - 0
proto/host/env.proto

@@ -36,6 +36,11 @@ service EnvService {
 
   // Logs a debug message to the host environment's log/output console.
   rpc debugLog(cline.StringRequest) returns (cline.Empty);
+
+  // Opens an external URL in the default browser.
+  // In remote environments (VS Code Server, SSH, etc.), this routes the URL
+  // to the user's local machine to open in their local browser.
+  rpc openExternal(cline.StringRequest) returns (cline.Empty);
 }
 
 message GetHostVersionResponse {

+ 8 - 0
src/hosts/vscode/hostbridge/env/openExternal.ts

@@ -0,0 +1,8 @@
+import { Empty, StringRequest } from "@shared/proto/cline/common"
+import * as vscode from "vscode"
+
+export async function openExternal(request: StringRequest): Promise<Empty> {
+	const uri = vscode.Uri.parse(request.value)
+	await vscode.env.openExternal(uri) // ← Routes to local browser in remote setups!
+	return Empty.create({})
+}

+ 2 - 2
src/utils/env.ts

@@ -1,5 +1,4 @@
 import { EmptyRequest, StringRequest } from "@shared/proto/cline/common"
-import open from "open"
 import { HostProvider } from "@/hosts/host-provider"
 import { Logger } from "@/shared/services/Logger"
 
@@ -41,5 +40,6 @@ export async function readTextFromClipboard(): Promise<string> {
  */
 export async function openExternal(url: string): Promise<void> {
 	Logger.log("Opening browser:", url)
-	await open(url)
+	// Use VS Code's openExternal which handles remote environments
+	await HostProvider.env.openExternal(StringRequest.create({ value: url }))
 }