From 681fb8fb0e3e592c0913b028e05370002fb2416a Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:28:36 -0500 Subject: [PATCH 1/2] fix(terminal): dispose grouped session when tmux attach fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit doStart creates the grouped …-ws- session before spawning tmux attach. If that spawn throws, tear the session down via dispose() instead of only marking DEAD — otherwise the orphan holds a pty until the next restart reaper. --- .../__tests__/isolated/terminalProxy.test.ts | 45 +++++++++++++++++++ src/server/terminal/PtyTerminalProxy.ts | 5 ++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/server/__tests__/isolated/terminalProxy.test.ts b/src/server/__tests__/isolated/terminalProxy.test.ts index dbeb1d71..1aa2a9e4 100644 --- a/src/server/__tests__/isolated/terminalProxy.test.ts +++ b/src/server/__tests__/isolated/terminalProxy.test.ts @@ -647,4 +647,49 @@ describe('TerminalProxy', () => { options: expect.objectContaining({ timeout: 15000 }), }) }) + + test('disposes the grouped session when tmux attach spawn fails', async () => { + const harness = createSpawnHarness() + const failingSpawn = ( + args: string[], + options: Parameters[1] + ) => { + harness.spawnCalls.push({ args, options }) + throw new Error('pty exhausted') + } + + const proxy = new TerminalProxy({ + connectionId: 'attach-fail', + sessionName: 'agentboard-ws-attach-fail', + baseSession: 'agentboard', + onData: () => {}, + spawn: failingSpawn, + spawnSync: harness.spawnSync, + wait: async () => {}, + }) + + await expect(proxy.start()).rejects.toMatchObject({ + code: 'ERR_TMUX_ATTACH_FAILED', + }) + + // new-session created the grouped session before attach; dispose must + // kill it so a failed spawn cannot orphan a pty-holding session. + expect(harness.spawnSyncCalls).toContainEqual({ + args: [ + 'tmux', + 'new-session', + '-d', + '-t', + 'agentboard', + '-s', + 'agentboard-ws-attach-fail', + ], + options: expect.objectContaining({ timeout: 15000 }), + }) + expect(harness.spawnSyncCalls).toContainEqual({ + args: ['tmux', 'kill-session', '-t', 'agentboard-ws-attach-fail'], + options: expect.objectContaining({ timeout: 15000 }), + }) + expect(proxy.isReady()).toBe(false) + }) }) diff --git a/src/server/terminal/PtyTerminalProxy.ts b/src/server/terminal/PtyTerminalProxy.ts index bed7eb7a..be6bdb14 100644 --- a/src/server/terminal/PtyTerminalProxy.ts +++ b/src/server/terminal/PtyTerminalProxy.ts @@ -240,7 +240,10 @@ class PtyTerminalProxy extends TerminalProxyBase { }, }) } catch (error) { - this.state = TerminalState.DEAD + // Tear down the grouped session created just above; otherwise a failed + // attach orphans a `…-ws-` session that holds a pty until the next + // restart's reaper. + await this.dispose() throw new TerminalProxyError( 'ERR_TMUX_ATTACH_FAILED', error instanceof Error ? error.message : 'Failed to attach tmux client', From 20e3dee90b44fbfca9a0d25c0a5ca112e5a1b424 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:31:18 -0500 Subject: [PATCH 2/2] chore(lockfile): align optional platform pins with package.json 0.4.4 package.json optionalDependencies were bumped to 0.4.4 but bun.lock still listed 0.4.3, so `bun install --frozen-lockfile` fails on CI (and locally). --- bun.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index fae6c5a2..0909270f 100644 --- a/bun.lock +++ b/bun.lock @@ -52,10 +52,10 @@ "vite-plugin-pwa": "^1.2.0", }, "optionalDependencies": { - "@gbasin/agentboard-darwin-arm64": "0.4.3", - "@gbasin/agentboard-darwin-x64": "0.4.3", - "@gbasin/agentboard-linux-arm64": "0.4.3", - "@gbasin/agentboard-linux-x64": "0.4.3", + "@gbasin/agentboard-darwin-arm64": "0.4.4", + "@gbasin/agentboard-darwin-x64": "0.4.4", + "@gbasin/agentboard-linux-arm64": "0.4.4", + "@gbasin/agentboard-linux-x64": "0.4.4", }, }, },