From 768a01326030cc6d2f7be835c539e17a93e862f8 Mon Sep 17 00:00:00 2001 From: Simon Gagnon Date: Tue, 31 Mar 2026 13:49:55 -0400 Subject: [PATCH] feat: add server_url_override config for tmux integration Add server_url_override option to tmux configuration to allow users to override the auto-detected server URL. This fixes issues where opencode binds to VPN/Tailscale IPs with TLS certificate errors, preventing the tmux health check from succeeding. Changes: - Add server_url_override field to TmuxConfigSchema - Update TmuxSessionManager to use override URL when provided - Pass server_url_override through plugin config initialization Users can now set tmux.server_url_override to "http://localhost:4096" to force localhost instead of using auto-detected IPs. --- src/config/schema/tmux.ts | 11 +++++++++++ src/features/tmux-subagent/manager.ts | 26 ++++++++++++++++++-------- src/index.ts | 1 + src/plugin/types.ts | 1 + 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/config/schema/tmux.ts b/src/config/schema/tmux.ts index 77582cf408..09c97d6869 100644 --- a/src/config/schema/tmux.ts +++ b/src/config/schema/tmux.ts @@ -21,6 +21,17 @@ export const TmuxConfigSchema = z.object({ main_pane_min_width: z.number().min(40).default(120), agent_pane_min_width: z.number().min(20).default(40), isolation: TmuxIsolationSchema.default("session"), + /** + * Override the server URL used for health checks and pane spawning. + * Use this when opencode auto-detects the wrong IP (e.g., Tailscale). + * Example: "http://localhost:4096" + */ + server_url_override: z + .preprocess( + (val) => (val === "" || val === null ? undefined : val), + z.string().url().optional() + ) + .optional(), }) export type TmuxConfig = z.infer diff --git a/src/features/tmux-subagent/manager.ts b/src/features/tmux-subagent/manager.ts index 0777417673..c75826b89c 100644 --- a/src/features/tmux-subagent/manager.ts +++ b/src/features/tmux-subagent/manager.ts @@ -77,17 +77,27 @@ export class TmuxSessionManager { this.deps = deps const defaultPort = process.env.OPENCODE_PORT ?? "4096" const fallbackUrl = `http://localhost:${defaultPort}` - try { - const raw = ctx.serverUrl?.toString() - if (raw) { - const parsed = new URL(raw) + if (tmuxConfig.server_url_override) { + try { + const parsed = new URL(tmuxConfig.server_url_override) const port = parsed.port || (parsed.protocol === 'https:' ? '443' : '80') - this.serverUrl = port === '0' ? fallbackUrl : raw - } else { + this.serverUrl = port === '0' ? fallbackUrl : tmuxConfig.server_url_override + } catch { + this.serverUrl = fallbackUrl + } + } else { + try { + const raw = ctx.serverUrl?.toString() + if (raw) { + const parsed = new URL(raw) + const port = parsed.port || (parsed.protocol === 'https:' ? '443' : '80') + this.serverUrl = port === '0' ? fallbackUrl : raw + } else { + this.serverUrl = fallbackUrl + } + } catch { this.serverUrl = fallbackUrl } - } catch { - this.serverUrl = fallbackUrl } this.sourcePaneId = deps.getCurrentPaneId() this.pollingManager = new TmuxPollingManager( diff --git a/src/index.ts b/src/index.ts index 1a080167a1..e5e60b1a57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { main_pane_min_width: pluginConfig.tmux?.main_pane_min_width ?? 120, agent_pane_min_width: pluginConfig.tmux?.agent_pane_min_width ?? 40, isolation: pluginConfig.tmux?.isolation ?? "session", + server_url_override: pluginConfig.tmux?.server_url_override, } const modelCacheState = createModelCacheState() diff --git a/src/plugin/types.ts b/src/plugin/types.ts index b713781bd3..30359cbe5e 100644 --- a/src/plugin/types.ts +++ b/src/plugin/types.ts @@ -23,4 +23,5 @@ export type TmuxConfig = { main_pane_min_width: number agent_pane_min_width: number isolation: "inline" | "window" | "session" + server_url_override?: string }