From 3f4c753b6cf59ed807df9b18a3d1805a66152872 Mon Sep 17 00:00:00 2001 From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:11:20 +0200 Subject: [PATCH] fix: re-raise SIGINT/SIGTERM after plugin cleanup Signal listeners suppressed the runtime default exit, so opencode web/serve survived SIGTERM indefinitely (#408). After cleanup, drop our listeners and re-deliver the signal without process.exit (#114). --- src/index.ts | 8 +++++++- tests/plugin-shutdown.test.ts | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 1dc2ac12..0480bfcf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -591,13 +591,19 @@ export const OpenCodeMemPlugin: Plugin = async (ctx: PluginInput) => { if (memoryClient) await memoryClient.close(); }; - const shutdownHandler = async () => { + const shutdownHandler = async (signal: NodeJS.Signals) => { try { await cleanupPlugin(); } catch (error) { log("Shutdown error", { error: String(error) }); process.exitCode = 1; } + // Registering listeners disables the runtime's default termination. + // After cleanup, remove ours and re-raise so the host can exit (#408). + // Avoid forced process exit here — that crashes onnxruntime-node finalizers (#114). + process.off("SIGINT", shutdownHandler); + process.off("SIGTERM", shutdownHandler); + process.kill(process.pid, signal); }; const beforeExitHandler = () => { diff --git a/tests/plugin-shutdown.test.ts b/tests/plugin-shutdown.test.ts index 940d3100..1af87ab4 100644 --- a/tests/plugin-shutdown.test.ts +++ b/tests/plugin-shutdown.test.ts @@ -8,6 +8,16 @@ describe("plugin shutdown", () => { expect(source).not.toContain("process.exit("); }); + it("re-raises SIGINT/SIGTERM after cleanup so the host can exit", () => { + const source = readFileSync(new URL("../src/index.ts", import.meta.url), "utf-8"); + + // Listeners suppress default termination; after cleanup we must drop them + // and re-deliver the signal (#408) without process.exit (#114). + expect(source).toContain('process.off("SIGINT", shutdownHandler)'); + expect(source).toContain('process.off("SIGTERM", shutdownHandler)'); + expect(source).toContain("process.kill(process.pid, signal)"); + }); + it("clears pending idle auto-capture work during cleanup", () => { const source = readFileSync(new URL("../src/index.ts", import.meta.url), "utf-8");