Skip to content

fix(opencode-native): stop leaking opencode serve processes across teardown paths - #4014

Open
TomeHirata wants to merge 1 commit into
mainfrom
fix-opencode-process
Open

fix(opencode-native): stop leaking opencode serve processes across teardown paths#4014
TomeHirata wants to merge 1 commit into
mainfrom
fix-opencode-process

Conversation

@TomeHirata

Copy link
Copy Markdown
Contributor

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 serve subprocess (tracked in _AUTO_OPENCODE_SERVERS) plus the opencode TUI pane. Only the DELETE /v1/sessions teardown 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:

  • idle pane reaper — closed the tmux pane but never touched _AUTO_OPENCODE_SERVERS;
  • unexpected TUI exit (crash / OOM / host recycle) — evicted the pane without cancelling the forwarder;
  • graceful host/runner stop — tore the runner down without a per-session DELETE, so _stop_pm never closed the servers.

This mirrors the codex fix:

  • teardown_opencode_native_server(session_id) — cancel the session's forwarder (whose finally closes 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.
  • Wired into the idle-reaper reap path, the terminal-exit publisher, and _stop_pm alongside the existing codex calls.

No boot-time reconcile (unlike codex): opencode has no crash-safe process registry, and opencode serve is a plain subprocess.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.

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 — passed
  • pytest tests/runner/test_app_sessions_native_terminals_runtime.py — 44 passed (3 pre-existing pi_cwd failures, unrelated — verified failing on the clean base too)
  • python -c "import omnigent.runner._entry; import omnigent.runner.app" — clean
  • pre-commit run on 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 — confirm pgrep -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

  • Bug fix
  • Feature
  • UI / frontend change
  • Refactor / chore
  • Docs
  • Test / CI
  • Breaking change

Test coverage

  • Unit tests added / updated
  • Integration tests added / updated
  • E2E tests added / updated
  • Manual verification completed
  • Existing tests cover this change
  • Not applicable

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_servers closes every session and is idempotent). The reaper / terminal-exit / _stop_pm wirings 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 serve processes after idle reaping, TUI exit, or runner/host shutdown

Copilot AI lite review requested due to automatic review settings August 4, 2026 04:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the size/L Pull request size: L label Aug 4, 2026
@omnigent-ci

omnigent-ci Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Polly AI Review

Summary

Solid, low-risk follow-up that closes a real leak. teardown_opencode_native_server / teardown_all_opencode_native_servers are faithful mirrors of the codex helpers, and I confirmed the underlying facts the PR relies on: _supervise_opencode_forwarder's finally pops and closes the server (so cancel-then-pop is not a double-close, and contextlib.suppress covers the belt-and-suspenders path anyway), and opencode serve is launched as a plain subprocess.Popen with no start_new_session=True (omnigent/opencode_native_app_server.py:487), which correctly justifies skipping the codex-style boot reconcile. The three wiring points (idle reaper, terminal-exit publisher, _stop_pm) sit directly beside the existing codex calls, and the new tests exercise the two behaviours worth pinning (per-session cancel+close, no-op-without-registration, shutdown sweep + idempotency). No demonstration is needed — this is a backend process-lifecycle fix with no user-visible surface.

Blocking issues

None found.

Security vulnerabilities

None. No new external input, deserialization, or boundary changes; teardown only cancels tasks and closes runner-owned subprocesses.

Non-blocking notes

  • Untested wirings. The reaper / terminal-exit / _stop_pm call sites are only manually verified (the PR is candid about this). Reasonable given they're closure-local and identical to the merged codex wiring, but the idle-reaper path in particular is the primary observed-leak path — if tests/terminals/test_pane_reaper.py has any hook to assert _native_runtime.teardown_opencode_native_server was invoked, it'd be worth a one-line assertion to prevent silent regression if the two finally calls ever drift.
  • Concurrent teardown paths. The terminal-exit publisher fires teardown as a background asyncio.create_task while the reaper awaits it inline, so both can target the same session. This is safe (the if session_id not in _AUTO_OPENCODE_SERVERS: return guard + atomic pop + suppressed close make it a benign no-op on the loser), just noting it's an intentional, harmless race rather than an oversight.
  • Hard runner death still leaks nothing new. The PR correctly notes a hard (SIGKILL) runner death takes the shared process group down with it since there's no start_new_session. That's the right call and matches the code; no action needed.

Approve.


Automated review by Polly · workflow run

@TomeHirata
TomeHirata force-pushed the fix-opencode-process branch from 4b98fcb to cb5816c Compare August 5, 2026 12:53
Copilot AI review requested due to automatic review settings August 5, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…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>
Copilot AI review requested due to automatic review settings August 5, 2026 13:22
@TomeHirata
TomeHirata force-pushed the fix-opencode-process branch from cb5816c to 6a6722a Compare August 5, 2026 13:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Pull request size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants