From eb8fb9f6ab8079652895c63ea3f068972e98c3d2 Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Sun, 26 Jul 2026 16:59:00 +0000 Subject: [PATCH] fix(agent-manager): detect agent at tmux pane PID --- .../src/__tests__/terminal/TmuxManager.test.ts | 10 ++++++++++ packages/agent-manager/src/terminal/TmuxManager.ts | 12 +++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/agent-manager/src/__tests__/terminal/TmuxManager.test.ts b/packages/agent-manager/src/__tests__/terminal/TmuxManager.test.ts index 8eeb9558..64e856a5 100644 --- a/packages/agent-manager/src/__tests__/terminal/TmuxManager.test.ts +++ b/packages/agent-manager/src/__tests__/terminal/TmuxManager.test.ts @@ -106,6 +106,16 @@ describe('TmuxManager', () => { expect(await tmux.findAgentPid('foo', matchesClaude)).toBeNull(); }); + it('returns the matching pane PID when the agent replaces the shell', async () => { + setExecFileHandler((cmd, args) => { + if (cmd === 'tmux' && args[0] === 'list-panes') return '100\n'; + if (cmd === 'pgrep') return new Error('no children'); + if (cmd === 'ps' && args[1] === '100') return '/usr/local/bin/claude'; + return ''; + }); + expect(await tmux.findAgentPid('foo', matchesClaude)).toBe(100); + }); + it('returns the matching descendant when found', async () => { // pane 100 → child 200 (claude) — no grandchildren setExecFileHandler((cmd, args) => { diff --git a/packages/agent-manager/src/terminal/TmuxManager.ts b/packages/agent-manager/src/terminal/TmuxManager.ts index 58b7ff6d..d0461955 100644 --- a/packages/agent-manager/src/terminal/TmuxManager.ts +++ b/packages/agent-manager/src/terminal/TmuxManager.ts @@ -41,7 +41,7 @@ export class TmuxManager { /** * Find the actual agent process PID inside a tmux pane. * - * Strategy: BFS the process tree, return the deepest descendant whose + * Strategy: BFS the process tree, return the deepest process whose * `ps` command line is accepted by `matches`. The caller supplies the * matcher so this method has no agent-type knowledge. * @@ -51,7 +51,7 @@ export class TmuxManager { * - Subprocess case: shell → claude (matches) → MCP server child (doesn't match) * → returns claude, not the subprocess * - * Returns null when no descendant matches yet (agent still starting); the + * Returns null when no process matches yet (agent still starting); the * caller's poll loop retries. */ async findAgentPid(session: string, matches: (psCommand: string) => boolean): Promise { @@ -67,11 +67,9 @@ export class TmuxManager { if (visited.has(pid)) continue; visited.add(pid); - if (pid !== panePid) { - const command = await this.getProcessCommand(pid); - if (command && matches(command)) { - deepestMatch = pid; - } + const command = await this.getProcessCommand(pid); + if (command && matches(command)) { + deepestMatch = pid; } const children = await this.pgrepChildren(pid);