diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 05714268545..847a73ef33f 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -109,6 +109,19 @@ export const TuiThreadCommand = cmd({ await client.call("reload", undefined) }) + // Handle termination signals to properly shutdown worker + const shutdown = async () => { + try { + await client.call("shutdown", undefined) + } catch { + // Ignore errors if worker is already dead + } + process.exit(0) + } + + process.on("SIGTERM", shutdown) + process.on("SIGINT", shutdown) + const prompt = await iife(async () => { const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined if (!args.prompt) return piped diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index e63f10ba80c..2f70197bbe0 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -32,6 +32,33 @@ process.on("uncaughtException", (e) => { }) }) +// Terminate worker if parent process dies +// Check periodically if parent is still alive +const parentPid = process.ppid +if (parentPid) { + const checkParent = setInterval(() => { + try { + // process.kill with signal 0 checks if process exists without killing it + process.kill(parentPid, 0) + } catch { + // Parent is dead, shutdown + Log.Default.info("parent process died, shutting down worker") + clearInterval(checkParent) + rpc.shutdown().finally(() => process.exit(0)) + } + }, 1000) + checkParent.unref() // Don't prevent exit +} + +// Handle termination signals in worker +const workerShutdown = async () => { + await rpc.shutdown() + process.exit(0) +} + +process.on("SIGTERM", workerShutdown) +process.on("SIGINT", workerShutdown) + // Subscribe to global events and forward them via RPC GlobalBus.on("event", (event) => { Rpc.emit("global.event", event)