diff --git a/src/create-managers.test.ts b/src/create-managers.test.ts index 075080606..1380e40b1 100644 --- a/src/create-managers.test.ts +++ b/src/create-managers.test.ts @@ -175,6 +175,28 @@ describe("createManagers", () => { expect(markServerRunningInProcess).toHaveBeenCalledTimes(1) }) + it("#given tmux is enabled but ctx.serverUrl is undefined #when managers are created #then it does NOT mark the server as running (issue #3894)", () => { + // Vanilla `opencode` (no `opencode serve` / `opencode web`) leaves + // ctx.serverUrl undefined. Marking the server as in-process running + // would short-circuit isServerRunning() in createTeamLayout, letting + // it spawn tmux panes whose `opencode attach` then fails because no + // server is actually listening on the fallback port. + const ctx = createContext("/tmp") + const ctxWithoutServerUrl = { ...ctx, serverUrl: undefined as unknown as URL } + const args = { + ctx: ctxWithoutServerUrl, + pluginConfig: OhMyOpenCodeConfigSchema.parse({}), + tmuxConfig: createTmuxConfig(true), + modelCacheState: createModelCacheState(), + backgroundNotificationHookEnabled: false, + deps: createDeps(), + } + + createManagers(args) + + expect(markServerRunningInProcess).not.toHaveBeenCalled() + }) + it("#given openclaw is enabled #when the background session-created callback runs #then it dispatches openclaw with the tracked pane id", async () => { const args = { ctx: createContext("/tmp/project"), diff --git a/src/create-managers.ts b/src/create-managers.ts index 842f6cfe3..40b752983 100644 --- a/src/create-managers.ts +++ b/src/create-managers.ts @@ -57,7 +57,15 @@ export function createManagers(args: { const { ctx, pluginConfig, tmuxConfig, modelCacheState, backgroundNotificationHookEnabled } = args const deps = { ...defaultCreateManagersDeps, ...args.deps } - if (tmuxConfig.enabled) { + // Only mark the server as in-process when the SDK actually exposes a + // serverUrl. `tmuxConfig.enabled` alone is not proof of a running server — + // a vanilla `opencode` session (no `opencode serve`/`opencode web`) leaves + // `ctx.serverUrl` undefined, and marking it running would make + // `isServerRunning` short-circuit to true. That bypasses the guard in + // `createTeamLayout` and lets it spawn tmux panes whose `opencode attach` + // command then fails because nothing is actually listening on the + // fallback port (issue #3894). + if (tmuxConfig.enabled && ctx.serverUrl) { deps.markServerRunningInProcessFn() } const tmuxSessionManager = new deps.TmuxSessionManagerClass(ctx, tmuxConfig)