fix(mcp): exit on uncaught exception instead of orphaning/spinning at 100% CPU (#855)
The process-wide uncaughtException handler logged the error and kept running. For the detached `serve --mcp` daemon that turned any escaped fault into an unrecoverable orphan: nothing respawns it, and when logging the raw Error hit a V8 source-position loop while lazily formatting `.stack`, the main thread wedged at 100% CPU so even the PPID watchdog / idle-timer could no longer fire. Same failure mode as #799, which only fixed the stdin-'error' trigger. Restore Node's default fatal semantics: render a bounded, hang-proof line (name + message only — never read `.stack`) then exit non-zero, so a fresh daemon starts on the next connection. Extracted to src/bin/fatal-handler.ts with injectable seams; unit-tested incl. the never-touch-stack invariant. Closes #850.
This commit is contained in:
@@ -32,6 +32,7 @@ import { createShimmerProgress } from '../ui/shimmer-progress';
|
||||
import { getGlyphs } from '../ui/glyphs';
|
||||
|
||||
import { buildNode25BlockBanner, buildNodeTooOldBanner, MIN_NODE_MAJOR } from './node-version-check';
|
||||
import { installFatalHandlers } from './fatal-handler';
|
||||
import { relaunchWithWasmRuntimeFlagsIfNeeded } from '../extraction/wasm-runtime-flags';
|
||||
import { EXTRACTION_VERSION } from '../extraction/extraction-version';
|
||||
import { getTelemetry, TELEMETRY_DOCS, recordIndexEvent } from '../telemetry';
|
||||
@@ -90,6 +91,13 @@ if (nodeMajor < MIN_NODE_MAJOR) {
|
||||
// inherits this process's flags) is compiled. See ../extraction/wasm-runtime-flags.
|
||||
relaunchWithWasmRuntimeFlagsIfNeeded(__filename);
|
||||
|
||||
// Last-resort fatal handlers: log a bounded line and exit non-zero. A fault
|
||||
// that reaches here escaped every boundary, so the process is in an undefined
|
||||
// state — keeping it alive is what let the detached MCP daemon orphan and pin a
|
||||
// CPU core with no recovery (#799, #850). Installed before the command branch
|
||||
// so it also covers a synchronous throw during startup. See ./fatal-handler.
|
||||
installFatalHandlers();
|
||||
|
||||
// Check if running with no arguments - run installer
|
||||
if (process.argv.length === 2) {
|
||||
import('../installer').then(({ runInstaller }) =>
|
||||
@@ -103,14 +111,6 @@ if (process.argv.length === 2) {
|
||||
main();
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('[CodeGraph] Uncaught exception:', error);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error('[CodeGraph] Unhandled rejection:', reason);
|
||||
});
|
||||
|
||||
function main() {
|
||||
|
||||
const program = new Command();
|
||||
|
||||
Reference in New Issue
Block a user