From b159c23afba0c0c3dbf995650f9404c14e53d33b Mon Sep 17 00:00:00 2001 From: "Aaron K. Clark" Date: Tue, 19 May 2026 07:41:02 -0500 Subject: [PATCH] chore(server): tighten SHUTDOWN_TIMEOUT_MS parsing to non-negative integers only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit \`parseInt(...) || 25_000\` had two latent edge cases: - \`SHUTDOWN_TIMEOUT_MS=-100\`: \`parseInt('-100', 10) === -100\`, truthy, so it was used verbatim as the setTimeout delay. \`setTimeout(-100, fn)\` fires immediately — the drain never gets a chance to run; the force-exit kills the process before server.close() or db.sequelize.close() complete. - \`SHUTDOWN_TIMEOUT_MS=0\`: zero is falsy, so it fell through to the 25_000 default. Surprising for an operator who set it expecting "no drain" semantics, but defensible. Apply the same Number.isFinite + >= 0 guard used for PORT (#124). Now only NaN / negative / unset values fall back to the default; \`SHUTDOWN_TIMEOUT_MS=0\` honors the operator's intent and force-exits immediately on signal. Co-Authored-By: Claude Opus 4.7 (1M context) --- server.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 76001b1..9e217a7 100644 --- a/server.js +++ b/server.js @@ -228,7 +228,15 @@ const server = app.listen(port, host, () => { // window), we force-exit with code 1. SIGINT (Ctrl-C in dev) follows // the same path so dev shutdowns aren't dirty either. -const shutdownTimeoutMs = parseInt(process.env.SHUTDOWN_TIMEOUT_MS, 10) || 25_000; +// `parseInt(...) || 25_000` accepts negative integers (parseInt('-100') +// = -100 is truthy, so it'd be used as the timeout — setTimeout(-100) +// fires immediately, force-exiting before the drain has a chance to +// run). Guard with the same Number.isFinite + >= 0 check used for +// PORT (#124) so only NaN and negatives fall back to the default. +const shutdownTimeoutRaw = parseInt(process.env.SHUTDOWN_TIMEOUT_MS, 10); +const shutdownTimeoutMs = Number.isFinite(shutdownTimeoutRaw) && shutdownTimeoutRaw >= 0 + ? shutdownTimeoutRaw + : 25_000; let shuttingDown = false; async function shutdown(signal) {