From c8e99cb31c38927a0b8fbd1ec0e27ba28bf34f44 Mon Sep 17 00:00:00 2001 From: Pedro Pombeiro Date: Mon, 9 Feb 2026 23:35:42 +0100 Subject: [PATCH 1/2] fix: handle signals to avoid orphaned processes --- packages/opencode/src/index.ts | 3 +++ packages/opencode/src/signal.ts | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 packages/opencode/src/signal.ts diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 6dc5e99e91ef..d9806814deb6 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -26,6 +26,7 @@ import { EOL } from "os" import { WebCommand } from "./cli/cmd/web" import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" +import { registerSignalHandlers } from "./signal" process.on("unhandledRejection", (e) => { Log.Default.error("rejection", { @@ -39,6 +40,8 @@ process.on("uncaughtException", (e) => { }) }) +registerSignalHandlers() + const cli = yargs(hideBin(process.argv)) .parserConfiguration({ "populate--": true }) .scriptName("opencode") diff --git a/packages/opencode/src/signal.ts b/packages/opencode/src/signal.ts new file mode 100644 index 000000000000..5075353b710d --- /dev/null +++ b/packages/opencode/src/signal.ts @@ -0,0 +1,45 @@ +import { Instance } from "./project/instance" +import { Log } from "./util/log" + +const SHUTDOWN_TIMEOUT_MS = 5000 + +const SIGNAL_EXIT_CODES: Record = { + SIGTERM: 128 + 15, + SIGINT: 128 + 2, + SIGHUP: 128 + 1, +} + +let shuttingDown = false + +async function gracefulShutdown(signal: string) { + if (shuttingDown) return + shuttingDown = true + + const log = Log.create({ service: "signal" }) + log.info("received signal, shutting down gracefully", { signal }) + + const timeout = setTimeout(() => { + log.warn("shutdown timeout, forcing exit", { + timeoutMs: SHUTDOWN_TIMEOUT_MS, + signal, + }) + process.exit(SIGNAL_EXIT_CODES[signal] ?? 1) + }, SHUTDOWN_TIMEOUT_MS) + + try { + await Instance.disposeAll() + } catch (error) { + log.error("error during shutdown", { error }) + } finally { + clearTimeout(timeout) + process.exit(SIGNAL_EXIT_CODES[signal] ?? 0) + } +} + +export function registerSignalHandlers() { + for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) { + process.on(signal, () => { + void gracefulShutdown(signal) + }) + } +} From bcca192b6d92a014a5e0a014cb6eb0fe012a671d Mon Sep 17 00:00:00 2001 From: Pedro Pombeiro Date: Mon, 9 Feb 2026 23:38:41 +0100 Subject: [PATCH 2/2] fix: add SIGQUIT handler and unref timeout --- packages/opencode/src/signal.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/signal.ts b/packages/opencode/src/signal.ts index 5075353b710d..9c84cb1788b8 100644 --- a/packages/opencode/src/signal.ts +++ b/packages/opencode/src/signal.ts @@ -7,6 +7,7 @@ const SIGNAL_EXIT_CODES: Record = { SIGTERM: 128 + 15, SIGINT: 128 + 2, SIGHUP: 128 + 1, + SIGQUIT: 128 + 3, } let shuttingDown = false @@ -25,6 +26,7 @@ async function gracefulShutdown(signal: string) { }) process.exit(SIGNAL_EXIT_CODES[signal] ?? 1) }, SHUTDOWN_TIMEOUT_MS) + timeout.unref() try { await Instance.disposeAll() @@ -37,7 +39,7 @@ async function gracefulShutdown(signal: string) { } export function registerSignalHandlers() { - for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) { + for (const signal of ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT"]) { process.on(signal, () => { void gracefulShutdown(signal) })