fix(team-mode): skip tmux layout when opencode server unreachable

`createManagers` unconditionally called `markServerRunningInProcess()`
whenever `tmuxConfig.enabled` was true, which made `isServerRunning()`
short-circuit to `true` for *any* serverUrl — bypassing the guard in
`createTeamLayout` that is supposed to skip pane creation when the
opencode server is not running.

When a user launches vanilla `opencode` (no `opencode serve` /
`opencode web`), `ctx.serverUrl` is undefined and the TmuxSessionManager
falls back to `http://localhost:4096`. With the in-process flag set, the
team layout proceeded to spawn tmux panes whose `opencode attach`
commands then failed with "Unable to connect" — exactly the symptom in
the bug report.

Only mark the server as in-process running when the SDK actually
provides `ctx.serverUrl`. When it does not, `isServerRunning()` falls
back to a real HTTP probe, the existing guard in `createTeamLayout`
returns null, and no panes are created.

Closes #3894
This commit is contained in:
PeterPonyu
2026-05-15 06:46:07 -04:00
parent 2b43147c41
commit bd3928e158
2 changed files with 31 additions and 1 deletions
+22
View File
@@ -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"),
+9 -1
View File
@@ -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)