Summary
ensureServer in plugins/opencode/scripts/lib/opencode-server.mjs:93-99 spawns opencode serve with stdio: "ignore", detached: true, and proc.unref(). Nothing in the plugin ever kills that server. On SessionEnd, plugins/opencode/scripts/session-lifecycle-hook.mjs only reaps job records; the opencode serve process keeps running and holding port 4096.
This is the OpenCode equivalent of the "process outlives session" cluster upstream (openai/codex-plugin-cc#108 for the app-server broker, #193 for the orphaned codex binary). The failure mode here is milder — the server is idle HTTP, not a CPU busy-loop — but over time users accumulate orphaned opencode serve processes that hold a port the user may also try to use manually.
Complication
We can't unconditionally kill the server on SessionEnd because the user may have started it themselves outside the plugin (isServerRunning check in ensureServer already reuses a pre-existing server). We should only kill what we started.
Suggested fix
- When
ensureServer actually spawns a new process, persist the PID into workspace state with a startedByPlugin: true flag.
- On
SessionEnd, if the persisted PID belongs to the last plugin-spawned server and no other Claude session is active against the same port, process.kill(pid, "SIGTERM").
- Consider an idle-timeout-based self-shutdown as a defense in depth.
Upstream references
Class derived from openai/codex-plugin-cc#108 and #193 — opencode equivalent.
Summary
ensureServerinplugins/opencode/scripts/lib/opencode-server.mjs:93-99spawnsopencode servewithstdio: "ignore",detached: true, andproc.unref(). Nothing in the plugin ever kills that server. OnSessionEnd,plugins/opencode/scripts/session-lifecycle-hook.mjsonly reaps job records; theopencode serveprocess keeps running and holding port 4096.This is the OpenCode equivalent of the "process outlives session" cluster upstream (openai/codex-plugin-cc#108 for the app-server broker, #193 for the orphaned codex binary). The failure mode here is milder — the server is idle HTTP, not a CPU busy-loop — but over time users accumulate orphaned
opencode serveprocesses that hold a port the user may also try to use manually.Complication
We can't unconditionally kill the server on SessionEnd because the user may have started it themselves outside the plugin (
isServerRunningcheck inensureServeralready reuses a pre-existing server). We should only kill what we started.Suggested fix
ensureServeractually spawns a new process, persist the PID into workspace state with astartedByPlugin: trueflag.SessionEnd, if the persisted PID belongs to the last plugin-spawned server and no other Claude session is active against the same port,process.kill(pid, "SIGTERM").Upstream references
Class derived from openai/codex-plugin-cc#108 and #193 — opencode equivalent.