From f440f3a5dd200cf879e2713247e855b2d06d9c81 Mon Sep 17 00:00:00 2001 From: ClineXDiego Date: Thu, 5 Feb 2026 00:31:20 -0300 Subject: [PATCH] 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 --- .changeset/fix-vscode-server-auth.md | 7 +++++++ cli/src/acp/ACPHostBridgeClientProvider.ts | 10 ++++++++++ cli/src/controllers/index.ts | 11 +++++++++++ cli/src/utils/browser.ts | 10 ++++++++++ proto/host/env.proto | 5 +++++ src/hosts/vscode/hostbridge/env/openExternal.ts | 8 ++++++++ src/utils/env.ts | 4 ++-- 7 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-vscode-server-auth.md create mode 100644 cli/src/utils/browser.ts create mode 100644 src/hosts/vscode/hostbridge/env/openExternal.ts diff --git a/.changeset/fix-vscode-server-auth.md b/.changeset/fix-vscode-server-auth.md new file mode 100644 index 0000000000..0b3d11a994 --- /dev/null +++ b/.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. diff --git a/cli/src/acp/ACPHostBridgeClientProvider.ts b/cli/src/acp/ACPHostBridgeClientProvider.ts index e487d62aa8..5ed3acc284 100644 --- a/cli/src/acp/ACPHostBridgeClientProvider.ts +++ b/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 { + 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() + } } /** diff --git a/cli/src/controllers/index.ts b/cli/src/controllers/index.ts index 2ea5ebc277..cae16efe13 100644 --- a/cli/src/controllers/index.ts +++ b/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 { + 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() + } } /** diff --git a/cli/src/utils/browser.ts b/cli/src/utils/browser.ts new file mode 100644 index 0000000000..3e10aee231 --- /dev/null +++ b/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 { + const { default: open } = await import("open") + await open(url) +} diff --git a/proto/host/env.proto b/proto/host/env.proto index a8e234964f..2c5a229448 100644 --- a/proto/host/env.proto +++ b/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 { diff --git a/src/hosts/vscode/hostbridge/env/openExternal.ts b/src/hosts/vscode/hostbridge/env/openExternal.ts new file mode 100644 index 0000000000..d3c6841a3f --- /dev/null +++ b/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 { + const uri = vscode.Uri.parse(request.value) + await vscode.env.openExternal(uri) // ← Routes to local browser in remote setups! + return Empty.create({}) +} diff --git a/src/utils/env.ts b/src/utils/env.ts index d6cc50a180..682b77de48 100644 --- a/src/utils/env.ts +++ b/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 { */ export async function openExternal(url: string): Promise { 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 })) }