Files
openchamber/scripts/repro/issue-2638/README.md
T
Serhii Dziupin 13f6a0280d 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
2026-08-05 13:59:17 +03:00

48 lines
2.2 KiB
Markdown

# Reproduction: Chat UI stops updating until the desktop app is restarted (#2638)
Reproduces https://github.com/openchamber/openchamber/issues/2638 using the
real server modules (`lifecycle.js`, `global-hub.js`, `network-runtime.js`),
real processes, and real ports — no mocks.
## Run
```sh
# Windows-orphan scenario (the reported bug):
node scripts/repro/issue-2638/reproduce-2638.mjs
# Control: healthy restart on Linux (hub reconnects, UI keeps updating):
node scripts/repro/issue-2638/reproduce-2638.mjs --baseline
```
Requires `lsof` (used only for cleanup). The default run simulates Windows
(`process.platform` is temporarily overridden to `win32`) because the bug is
specific to the Windows process-lifecycle path.
## What it demonstrates
1. A managed OpenCode process starts; the global message-stream hub connects to
its `/global/event` SSE stream and chat events flow to the UI.
2. The managed process "exits" but the actual server process survives on the
old port (on Windows `killProcessOnPort` is a no-op and `taskkill` cannot
reach the orphaned tree — the report shows leftover `opencode.exe serve`
processes on historical ports).
3. `restartOpenCode()` gives up after 5 s — logs
`Timed out waiting for OpenCode port <old> to be released` — and spawns a
fresh server on a NEW port, leaving the orphaned process running.
4. HTTP/proxy traffic follows `state.openCodePort` to the new server, but the
hub's upstream SSE reader stays pinned to the OLD server's `/global/event`
stream (that connection never closed), so events emitted by the new server
never reach the UI: the chat UI goes stale while the new server keeps
persisting session data — visible only after restarting the app.
The `--baseline` control proves the reconnect logic itself is fine: when the
old process dies and the port is properly released, the hub reconnects to the
new port and events are delivered.
## Files
- `reproduce-2638.mjs` — the reproduction driver (assertions + summary).
- `fake-opencode-serve.mjs` — a fake `opencode serve` binary whose launcher
spawns a detached server core that survives the launcher's death
(Windows-style orphan), plus an in-process mode for the baseline control.