Skip to content

Commit 588ce01

Browse files
committed
fix: address review — filter ESRCH, hoist cleanup registration
- PPID poll: only exit on ESRCH (process gone), ignore EPERM (process alive but different UID — setuid, Linux security modules, etc.) - Hoist stopAllWatchers + exit/signal listeners to before stdin/onclose handlers, closing the race window where process.exit() could fire during initProject() before the cleanup listener was registered
1 parent 0704a0b commit 588ce01

File tree

1 file changed

+27
-23
lines changed

1 file changed

+27
-23
lines changed

src/index.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,8 +1619,11 @@ async function main() {
16191619
const parentGuard = setInterval(() => {
16201620
try {
16211621
process.kill(parentPid, 0);
1622-
} catch {
1623-
process.exit(0);
1622+
} catch (err: unknown) {
1623+
// ESRCH = process gone → exit. EPERM = process alive, different UID → ignore.
1624+
if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
1625+
process.exit(0);
1626+
}
16241627
}
16251628
}, 5_000);
16261629
parentGuard.unref();
@@ -1629,6 +1632,28 @@ async function main() {
16291632
const transport = new StdioServerTransport();
16301633
await server.connect(transport);
16311634

1635+
// Register cleanup before any handler that calls process.exit(), so the
1636+
// exit listener is always in place when stdin/onclose/signals fire.
1637+
const stopAllWatchers = () => {
1638+
for (const project of getAllProjects()) {
1639+
project.stopWatcher?.();
1640+
}
1641+
};
1642+
1643+
process.once('exit', stopAllWatchers);
1644+
process.once('SIGINT', () => {
1645+
stopAllWatchers();
1646+
process.exit(0);
1647+
});
1648+
process.once('SIGTERM', () => {
1649+
stopAllWatchers();
1650+
process.exit(0);
1651+
});
1652+
process.once('SIGHUP', () => {
1653+
stopAllWatchers();
1654+
process.exit(0);
1655+
});
1656+
16321657
// Detect stdin pipe closure — the primary signal that the MCP client is gone.
16331658
// StdioServerTransport only listens for 'data'/'error', never 'end'.
16341659
process.stdin.on('end', () => process.exit(0));
@@ -1657,27 +1682,6 @@ async function main() {
16571682
/* best-effort */
16581683
}
16591684
});
1660-
1661-
// Cleanup all watchers on exit
1662-
const stopAllWatchers = () => {
1663-
for (const project of getAllProjects()) {
1664-
project.stopWatcher?.();
1665-
}
1666-
};
1667-
1668-
process.once('exit', stopAllWatchers);
1669-
process.once('SIGINT', () => {
1670-
stopAllWatchers();
1671-
process.exit(0);
1672-
});
1673-
process.once('SIGTERM', () => {
1674-
stopAllWatchers();
1675-
process.exit(0);
1676-
});
1677-
process.once('SIGHUP', () => {
1678-
stopAllWatchers();
1679-
process.exit(0);
1680-
});
16811685
}
16821686

16831687
// Export server components for programmatic use

0 commit comments

Comments
 (0)