Skip to content

Commit f2e1f70

Browse files
cameroncookecodex
andcommitted
fix(swift-package): Wait for real process exit after SIGTERM
Do not treat ChildProcess.killed as an exit signal when deciding whether\nto skip termination waits. The killed flag means a signal was sent,\nnot that the process has exited.\n\nUse exitCode/signalCode to detect actual exit, and keep bounded\nwait+SIGKILL behavior intact for stuck processes.\n\nAdd regression coverage that sets killed=true without an exit event\nand verifies we still escalate from SIGTERM to SIGKILL. Co-Authored-By: OpenAI Codex <noreply@openai.com>
1 parent 3bfa481 commit f2e1f70

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

src/mcp/tools/swift-package/__tests__/active-processes.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
addProcess,
1111
removeProcess,
1212
clearAllProcesses,
13+
terminateTrackedProcess,
1314
type ProcessInfo,
1415
} from '../active-processes.ts';
1516
import {
@@ -213,6 +214,34 @@ describe('active-processes module', () => {
213214
});
214215
});
215216

217+
describe('process termination helper', () => {
218+
it('does not treat killed=true as exited after SIGTERM', async () => {
219+
const signals: string[] = [];
220+
221+
addProcess(4242, {
222+
process: {
223+
kill: (signal?: string) => {
224+
signals.push(signal ?? 'SIGTERM');
225+
},
226+
on: () => {
227+
// never emits exit; allow timeout path
228+
},
229+
killed: true,
230+
exitCode: null,
231+
signalCode: null,
232+
pid: 4242,
233+
},
234+
startedAt: new Date('2023-01-01T00:00:00.000Z'),
235+
});
236+
237+
const result = await terminateTrackedProcess(4242, 25);
238+
239+
expect(result.status).toBe('terminated');
240+
expect(result.usedForceKill).toBe(true);
241+
expect(signals).toEqual(['SIGTERM', 'SIGKILL']);
242+
});
243+
});
244+
216245
describe('ProcessInfo interface', () => {
217246
it('should work with complete process object', () => {
218247
const mockProcess = {

src/mcp/tools/swift-package/active-processes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface TrackedProcess {
1111
removeListener?: (event: string, callback: () => void) => void;
1212
pid?: number;
1313
exitCode?: number | null;
14+
signalCode?: string | null;
1415
killed?: boolean;
1516
}
1617

@@ -64,7 +65,7 @@ async function terminateProcess(
6465
return { error: error instanceof Error ? error.message : String(error) };
6566
}
6667

67-
const alreadyExited = info.process.exitCode != null || info.process.killed === true;
68+
const alreadyExited = info.process.exitCode != null || info.process.signalCode != null;
6869
if (alreadyExited) {
6970
return {};
7071
}

0 commit comments

Comments
 (0)