opencode icon indicating copy to clipboard operation
opencode copied to clipboard

fix: use localhost instead of 127.0.0.1 for OAuth callback

Open dcramer opened this issue 3 weeks ago • 3 comments

This fixes the OAuth flow in WSL2 where 127.0.0.1 inside WSL is not accessible from the Windows host browser. Using localhost enables WSL2's built-in localhost forwarding between Windows and WSL.

🤖 Generated with Claude Code

dcramer avatar Dec 19 '25 04:12 dcramer

aside i dont know if localhost causes issues in some scenarios?

will run the generate command and update pr

dcramer avatar Dec 19 '25 04:12 dcramer

Likely a path here where I could just detect WSL but it might not be worth it. I think the "theres no dns/localhost" case is probably an outlier. Willing to be convinced, but I'm not sure the complexity of OS detection is worth it if its a 0.001% kind of thing.

example:

diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts
index c434b21ed..218aa5fec 100644
--- a/packages/opencode/src/mcp/oauth-callback.ts
+++ b/packages/opencode/src/mcp/oauth-callback.ts
@@ -1,5 +1,5 @@
 import { Log } from "../util/log"
-import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider"
+import { OAUTH_CALLBACK_HOST, OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider"
 
 const log = Log.create({ service: "mcp.oauth-callback" })
 
@@ -161,7 +161,7 @@ export namespace McpOAuthCallback {
   export async function isPortInUse(): Promise<boolean> {
     return new Promise((resolve) => {
       Bun.connect({
-        hostname: "localhost",
+        hostname: OAUTH_CALLBACK_HOST,
         port: OAUTH_CALLBACK_PORT,
         socket: {
           open(socket) {
diff --git a/packages/opencode/src/mcp/oauth-provider.ts b/packages/opencode/src/mcp/oauth-provider.ts
index 3661034cc..9324da977 100644
--- a/packages/opencode/src/mcp/oauth-provider.ts
+++ b/packages/opencode/src/mcp/oauth-provider.ts
@@ -7,9 +7,21 @@ import type {
 } from "@modelcontextprotocol/sdk/shared/auth.js"
 import { McpAuth } from "./auth"
 import { Log } from "../util/log"
+import { release } from "os"
 
 const log = Log.create({ service: "mcp.oauth" })
 
+function getLoopbackHost(): string {
+  // WSL requires localhost for Windows host browser -> WSL callback forwarding
+  // Pattern matches clipboard.ts:32
+  if (release().includes("WSL")) {
+    return "localhost"
+  }
+  // 127.0.0.1 is guaranteed to work without DNS resolution
+  return "127.0.0.1"
+}
+
+const OAUTH_CALLBACK_HOST = getLoopbackHost()
 const OAUTH_CALLBACK_PORT = 19876
 const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback"
 
@@ -32,7 +44,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
   ) {}
 
   get redirectUrl(): string {
-    return `http://localhost:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`
+    return `http://${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`
   }
 
   get clientMetadata(): OAuthClientMetadata {
@@ -151,4 +163,4 @@ export class McpOAuthProvider implements OAuthClientProvider {
   }
 }
 
-export { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH }
+export { OAUTH_CALLBACK_HOST, OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH }

dcramer avatar Dec 19 '25 17:12 dcramer