From e5f5bc3d31dcc77e59957b78de37727ccefdd58c Mon Sep 17 00:00:00 2001 From: teamchong <25894545+teamchong@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:34:35 -0400 Subject: [PATCH] fix(node): exit cleanly on Ctrl+C during in-flight streams and idle keep-alive server.close() waits for open connections to drain, so it never finishes while the dashboard tab polls every 2s or while an SSE response is mid-stream - the first Ctrl+C printed 'shutting down' but the process never exited. Drop idle keep-alive sockets immediately, force-close anything still streaming after a 1.5s grace, and make a second Ctrl+C an instant hard exit. --- src/node.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/node.ts b/src/node.ts index fe25b56..0b6d9bf 100644 --- a/src/node.ts +++ b/src/node.ts @@ -614,11 +614,32 @@ async function main(): Promise { console.log(`[pxpipe] dashboard → http://127.0.0.1:${opts.port}/`); }); + // server.close() only stops accepting new connections and waits for open + // ones to drain — it does NOT end idle keep-alive sockets. The dashboard tab + // (htmx polls every 2s) and the Claude Code client both hold keep-alive + // sockets open, so a naive close() never fires its callback and the first + // Ctrl+C appears to hang. We drop idle sockets immediately, force-close any + // in-flight ones after a short grace period, and let a second signal exit now. + let shuttingDown = false; const shutdown = (sig: string) => { + if (shuttingDown) { + console.log(`[pxpipe] ${sig} again — forcing exit`); + process.exit(130); + } + shuttingDown = true; console.log(`[pxpipe] ${sig} — shutting down`); // Flush+close the tracker so we don't drop the last few events on exit. if (tracker instanceof FileTracker) tracker.close(); server.close(() => process.exit(0)); + // Drop idle keep-alive sockets so close()'s callback can actually fire. + server.closeIdleConnections?.(); + // Hard deadline: if a streaming /v1/messages response (or slow upstream) + // is still in flight, force the rest closed and exit anyway. + const deadline = setTimeout(() => { + server.closeAllConnections?.(); + process.exit(0); + }, 1500); + deadline.unref(); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM'));