Skip to content

TUI: signal exits leak child processes #14237

@bradkk-safer

Description

@bradkk-safer

Description

The TUI entrypoint (tui/thread.ts) only runs cleanup through onExit, which
calls client.call("shutdown"). Signal paths (SIGINT, SIGTERM, SIGHUP)
have no handlers, so worker cleanup is skipped and child resources can survive.

Same lifecycle class as #14091, different command path.

Code evidence

packages/opencode/src/cli/cmd/tui/thread.ts:

  • SIGUSR2 handler exists (reload only).
  • No handlers for SIGINT, SIGTERM, or SIGHUP.
  • Cleanup only here:
onExit: async () => {
  await client.call("shutdown", undefined)
}

worker.ts already has proper cleanup in rpc.shutdown() (event stream abort,
Instance.disposeAll() with timeout, server.stop(true)), but thread.ts does
not guarantee it runs on signal exit.

Steps to reproduce

# 1) Start TUI under a terminal host
script -q -c "opencode /tmp/repro-project" /tmp/tui.log &
# 2) Send SIGHUP to TUI process
kill -HUP "$(pgrep -f 'opencode /tmp/repro-project')"
sleep 2
# 3) Check for surviving children
pgrep -fa 'fake-mcp'  # child MCP process still alive

(Full harness with a deterministic fake MCP server available on request.)

Expected behavior

TUI should run worker shutdown on signal/terminal-close paths so child resources
are disposed before process exit.

Actual behavior

Child resources can survive after signal exit because shutdown is not invoked.

OpenCode version

dev branch at 3a416f6f3

Operating System

Linux (signal semantics apply cross-platform; Windows uses different exit paths
already handled by win32 guards in the same file)

Metadata

Metadata

Assignees

Labels

opentuiThis relates to changes in v1.0, now that opencode uses opentui

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions