…ardown paths
Opencode-native has the same process-leak shape codex-native did (fixed in
#3925): each session runs a runner-owned `opencode serve` subprocess tracked
in _AUTO_OPENCODE_SERVERS plus the opencode TUI pane. Only DELETE /v1/sessions
cancelled the forwarder (whose finally closes the server); the other ways the
TUI pane goes away left the server orphaned for the runner's lifetime:
- the idle pane reaper closed the tmux pane but never touched
_AUTO_OPENCODE_SERVERS,
- an unexpected TUI exit (crash / OOM / host recycle) evicted the pane
without cancelling the forwarder, and
- a graceful host/runner stop tore the runner down without a per-session
DELETE, so _stop_pm never closed the servers.
Mirror the codex fix: add teardown_opencode_native_server (cancel the
forwarder, close any leftover registered server; no-op when none is
registered) and teardown_all_opencode_native_servers (shutdown sweep). Wire
them into the idle-reaper reap, the terminal-exit publisher, and _stop_pm
alongside the codex calls.
No boot-time reconcile: opencode has no crash-safe process registry and
`opencode serve` is a plain Popen (not start_new_session=True), so it shares
the runner's process group and dies with a hard runner death — the
graceful-stop + reaper + exit paths cover the observed leak.
Signed-off-by: Tomu Hirata <tomu.hirata@gmail.com>
Related issue
N/A
Summary
Follow-up to #3925. Opencode-native has the same process-leak shape codex-native did: each session runs a runner-owned
opencode servesubprocess (tracked in_AUTO_OPENCODE_SERVERS) plus the opencode TUI pane. Only theDELETE /v1/sessionsteardown cancelled the forwarder (whosefinallycloses the server). The other ways the TUI pane goes away left the server orphaned for the runner's lifetime:_AUTO_OPENCODE_SERVERS;_stop_pmnever closed the servers.This mirrors the codex fix:
teardown_opencode_native_server(session_id)— cancel the session's forwarder (whosefinallycloses the server) and close any leftover registered server. No-op when no opencode server is registered, so it's safe on the shared pane-teardown paths for every harness.teardown_all_opencode_native_servers()— shutdown sweep for_stop_pm._stop_pmalongside the existing codex calls.No boot-time reconcile (unlike codex): opencode has no crash-safe process registry, and
opencode serveis a plainsubprocess.Popen(notstart_new_session=True), so it shares the runner's process group and dies with a hard runner death. The graceful-stop + reaper + exit paths cover the observed leak.Test Plan
pytest tests/runner/test_app_sessions_native_wake_forwarders.py— 23 passed (incl. 3 new opencode tests: teardown cancels forwarder + closes server; no-op without a registered server; shutdown sweep closes every session + idempotent)pytest tests/terminals/test_pane_reaper.py— passedpytest tests/runner/test_app_sessions_native_terminals_runtime.py— 44 passed (3 pre-existingpi_cwdfailures, unrelated — verified failing on the clean base too)python -c "import omnigent.runner._entry; import omnigent.runner.app"— cleanpre-commit runon changed files — passed (pyrefly's 2 errors are in untouched files:routes_core.py,nimble_research.py)Manual: launch an opencode-native session, then (a) let it idle out (
OMNIGENT_NATIVE_PANE_IDLE_TIMEOUT_S=15) or kill the TUI pane, and (b) stop the runner gracefully — confirmpgrep -fl 'opencode serve'drops to zero instead of accumulating one per session.Demo
N/A — non-visual runner-side process-lifecycle fix.
Type of change
Test coverage
Coverage notes
New unit tests mirror the codex ones and cover the per-session teardown (cancel-forwarder + close-server; no-op when unregistered) and the shutdown sweep (
teardown_all_opencode_native_serverscloses every session and is idempotent). The reaper / terminal-exit /_stop_pmwirings are closure-local and hard to unit-test directly; verified manually by tracing each exit path and by the identical, already-merged codex wiring they sit beside. Existing reaper and native-terminal suites confirm no regression.Changelog
Fixed a leak where native OpenCode sub-agents left orphaned
opencode serveprocesses after idle reaping, TUI exit, or runner/host shutdown