fix(server): rebind message-stream upstreams after a managed OpenCode restart

When the managed OpenCode process exits but a server survives on the old
port (Windows: killProcessOnPort is a no-op, so the orphaned process tree
keeps the port), restartOpenCode() times out waiting for the port and
spawns a fresh server on a NEW port. HTTP/proxy traffic follows the new
port, but the global message-stream hub's upstream SSE reader stays pinned
to the old server's /global/event stream — that connection never closes —
so new events never reach the UI and the chat stops updating until the
app is restarted (#2638).

Lifecycle now fires an optional onOpenCodeRestarted hook after a
successful managed restart; index.js wires it to the new
messageStreamRuntime.rebindUpstream(), which restarts the shared hub
(its reader re-dials buildOpenCodeUrl → the current port) and closes
directory-scoped sockets so their per-connection readers rebuild against
the new port. External servers are untouched (their port cannot change).

Fixes #2638
This commit is contained in:
Serhii Dziupin
2026-08-05 13:59:17 +03:00
parent 34c221b07f
commit 13f6a0280d
8 changed files with 881 additions and 0 deletions
@@ -48,6 +48,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
getActiveSessionCount = () => 0,
reapManagedOrphanedProcesses = reapOrphanedProcesses,
getWarmupDirectories = async () => [],
onOpenCodeRestarted = null,
now = Date.now,
} = deps;
@@ -694,6 +695,17 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
setupProxy(state.expressApp);
ensureOpenCodeApiPrefix();
}
// The restart may have landed on a NEW port (the old one can remain
// occupied by an orphaned process, e.g. Windows killProcessOnPort is a
// no-op). Upstream event readers pinned to the old process would keep
// the UI silent forever, so rebind them to the current port. Best
// effort: a failure here must not fail the restart itself.
try {
onOpenCodeRestarted?.();
} catch (error) {
console.warn('Failed to rebind event stream after OpenCode restart:', error?.message ?? error);
}
})();
try {