diff --git a/browse/src/terminal-agent-control.ts b/browse/src/terminal-agent-control.ts index bb96310e0..ac2eb78a4 100644 --- a/browse/src/terminal-agent-control.ts +++ b/browse/src/terminal-agent-control.ts @@ -368,7 +368,8 @@ function agentStatus(record: AgentRecord, ownerPid?: number): 'owned' | 'gone' | if (ownerPid !== undefined && (record.ownerPid !== ownerPid || record.ownerStartTime !== readAgentStartTime(ownerPid))) return 'unknown'; const actual = agentProcessInfo(record.pid); if (!actual.startTime) return isAgentRecordLive(record) ? 'unknown' : 'gone'; - if (actual.startTime !== record.startTime) return 'gone'; + const ownsGeneration = actual.commandLine.split(/\s+/).some(arg => arg.replace(/^['"]|['"]$/g, '') === `--agent-gen=${record.gen}`); + if (actual.startTime !== record.startTime && !(process.platform === 'win32' && ownsGeneration)) return 'gone'; try { let state: string | undefined; if (process.platform === 'linux') { @@ -380,8 +381,7 @@ function agentStatus(record: AgentRecord, ownerPid?: number): 'owned' | 'gone' | if (state === 'Z') return 'gone'; } catch {} if (!isAgentRecordLive(record)) return 'gone'; - return actual.commandLine.split(/\s+/).some(arg => arg.replace(/^['"]|['"]$/g, '') === `--agent-gen=${record.gen}`) - ? 'owned' : 'unknown'; + return ownsGeneration ? 'owned' : 'unknown'; } export function isOurAgent(record: AgentRecord, ownerPid?: number): boolean { diff --git a/browse/test/process-liveness-windows.test.ts b/browse/test/process-liveness-windows.test.ts index fff426b3e..252f5fd1f 100644 --- a/browse/test/process-liveness-windows.test.ts +++ b/browse/test/process-liveness-windows.test.ts @@ -151,7 +151,8 @@ describe('process liveness probe (Windows terminal-agent leak)', () => { if (process.platform === 'win32') { expect(captured.env.BROWSE_OWNER_START_TIME).toMatch(/^\/Date\(\d+\)\/$/); expect(captured.env.BROWSE_OWNER_START_TIME).toBe(readAgentStartTime(process.pid)); - expect(powershellCalls).toEqual([]); + expect(powershellCalls.filter(call => call.includes(`ProcessId = ${process.pid}'`))).toEqual([]); + expect(powershellCalls.every(call => call.includes("ProcessId = 2147483647'"))).toBe(true); } else { expect(captured.env.BROWSE_OWNER_START_TIME).toBe('fixture-owner-start'); } @@ -252,12 +253,22 @@ describe('win32 terminal-agent identity (GSTA-139)', () => { expect(calls.signals).toEqual([]); }); } - onWin32({ ffi: '/Date(1761600000124)/', powershell: { status: 0, creationDate: birth, commandLine: ownedCommand } }, calls => { + onWin32({ ffi: '/Date(1761600000124)/', powershell: { status: 0, creationDate: birth, commandLine: 'bun run unrelated.ts' } }, calls => { expect(isAgentRecordGone(record())).toBe(true); expect(calls.signals).toEqual([]); }); }); + test('a start time that disagrees across readers still identifies the live agent by its generation, so no second agent starts', () => { + const daemonRecorded = '/Date(1761596400123)/'; + onWin32({ ffi: birth, powershell: { status: 0, creationDate: daemonRecorded, commandLine: ownedCommand } }, calls => { + expect(isAgentRecordGone(record(daemonRecorded))).toBe(false); + expect(isOurAgent(record(daemonRecorded))).toBe(true); + expect(stopAgentByRecord(record(daemonRecorded), 0)).toBe(true); + expect(calls.signals).toEqual(['SIGTERM']); + }); + }); + test('an unreadable start time is unknown: never signalled and never treated as gone', () => { onWin32({ ffi: undefined, powershell: { status: null } }, calls => { expect(readAgentStartTime(pid)).toBe('');