Description
Firing a SIGTERM at opencode causes the TUI to close but then the process sits and hangs.
When opencode's TUI receives SIGINT (kill -INT <pid>) or SIGTERM (kill <pid>), the main thread dies without shutting down the Bun Worker. The worker never calls Instance.disposeAll(), so Effect runtimes, child processes (MCP servers, LSP clients, PTYs), and file watchers are all orphaned.
Why Ctrl+C works but SIGTERM doesn't
The renderer runs in raw terminal mode with exitOnCtrlC: false, so Ctrl+C is just the byte \x03. OpenTUI catches it as a keyboard event and triggers the graceful exit path. SIGTERM is an OS-level signal that bypasses the terminal entirely, and nothing handles it.
Root cause
The exit() function in exit.tsx handles the full teardown: plugin disposal, renderer destroy, recap output, then resolving the tui() promise which cascades to stop() in thread.ts -> client.call("shutdown") -> Instance.disposeAll().
But only SIGHUP is wired to call exit(). SIGINT and SIGTERM have no handlers.
Locally Verified Fix
Add SIGINT and SIGTERM handlers next to the existing SIGHUP handler in exit.tsx:57. The exit() function is already idempotent (gated on task), so double-calling is safe.
Plugins
OpenCode version
dev@origin
Steps to reproduce
killall opencode
Screenshot and/or share link
No response
Operating System
Debian Linux/forky, Linux 6.19
Terminal
tmux + ghostty or kitty
Description
Firing a
SIGTERMat opencode causes the TUI to close but then the process sits and hangs.When opencode's TUI receives SIGINT (
kill -INT <pid>) or SIGTERM (kill <pid>), the main thread dies without shutting down the Bun Worker. The worker never callsInstance.disposeAll(), so Effect runtimes, child processes (MCP servers, LSP clients, PTYs), and file watchers are all orphaned.Why Ctrl+C works but SIGTERM doesn't
The renderer runs in raw terminal mode with
exitOnCtrlC: false, so Ctrl+C is just the byte\x03. OpenTUI catches it as a keyboard event and triggers the graceful exit path. SIGTERM is an OS-level signal that bypasses the terminal entirely, and nothing handles it.Root cause
The
exit()function inexit.tsxhandles the full teardown: plugin disposal, renderer destroy, recap output, then resolving thetui()promise which cascades tostop()inthread.ts->client.call("shutdown")->Instance.disposeAll().But only
SIGHUPis wired to callexit(). SIGINT and SIGTERM have no handlers.Locally Verified Fix
Add
SIGINTandSIGTERMhandlers next to the existingSIGHUPhandler inexit.tsx:57. Theexit()function is already idempotent (gated ontask), so double-calling is safe.Plugins
OpenCode version
dev@originSteps to reproduce
killall opencodeScreenshot and/or share link
No response
Operating System
Debian Linux/forky, Linux 6.19
Terminal
tmux + ghostty or kitty