Repository navigation
Description
What happened
OpenChamber Desktop on Windows supervises a managed OpenCode server. When that server becomes unhealthy while sessions are busy, OpenChamber eventually restarts it successfully, but every in-flight turn is silently stranded. The UI provides no terminal error, interrupted state, or retry action. The affected sessions only become usable after another user message.
This happened twice in one day and has occurred repeatedly before.
Environment
- OpenChamber: 1.18.4 (latest release)
- Bundled OpenCode: 1.18.18 (latest release)
- OS: Windows 11
- CPU: AMD Ryzen 9 9950X3D, 32 logical processors
- RAM: 61.7 GiB
- Commit limit: 111.7 GiB
Reproduction shape
- Run two or three concurrent long-lived agent sessions, particularly sessions producing large repository diffs or tool output.
- The managed OpenCode health endpoint stops responding.
- OpenChamber counts health failures while the sessions remain marked busy.
- After the failure threshold and two-minute stale-busy grace, OpenChamber force-restarts OpenCode.
- The new OpenCode process becomes healthy, but interrupted turns do not resume or transition to a visible aborted/error state.
The trigger is made more likely by the OpenCode summary-diff amplification tracked in anomalyco/opencode#42748, but restart recovery and diagnostics remain OpenChamber concerns.
Incident 1 (OpenChamber local time, UTC-03)
16:18:37 [lifecycle] periodic health check failed (1/20)
16:23:11 [proxy] connect ECONNREFUSED 127.0.0.1:<port>
16:26:23 [lifecycle] periodic health check failed (28/20)
16:26:23 [lifecycle] OpenCode unhealthy with 3 busy session(s) for > 2 min - forcing restart
16:26:28 [OpenCode] Launching managed server on a new port
16:26:29 Detected OpenCode port
The interrupted turn had no continuation until a new user interaction approximately 39 minutes later.
Incident 2
17:44:10 [lifecycle] periodic health check failed (1/20)
17:48:41 [proxy] connect ECONNREFUSED 127.0.0.1:<port>
17:51:50 [lifecycle] periodic health check failed (28/20)
17:51:50 [lifecycle] OpenCode unhealthy with 3 busy session(s) for > 2 min - forcing restart
17:51:50 [OpenCode] Launching managed server on a new port
17:51:51 Detected OpenCode port
Again, no continuation or visible terminal state appeared until a later user message. Windows event logs contained no sleep, shutdown, or application-crash event for either interval. OpenChamber itself stayed running.
Aggregate-only storage evidence
A read-only audit of the healthy OpenCode database found:
- Database: 2.016 GiB;
PRAGMA quick_check:ok; freelist pages: 0 - 26 sessions, 1,816 messages, 12,164 parts, 48,814 events
message.updated.1: 1,596,501,940 bytesinfo.summary.diffsinside those events: 1,592,917,364 bytes (99.8%)message.part.updated.1: 388,949,497 bytes- One message: 81 updates, 736.4 MiB total, 11.4 MiB maximum event
- Two active session aggregates: 998.5 MiB and 761.2 MiB
No payload content, identifiers, titles, or paths were inspected for these measurements. This is consistent with anomalyco/opencode#42748 and explains the server pressure, but not the silent post-restart UX.
Expected behavior
After a forced managed-server restart, OpenChamber should:
- Reconcile sessions that were busy before restart into an explicit interrupted/aborted state.
- Refresh their status and messages after rebinding the event stream.
- Show a persistent notification with retry/resend rather than silently leaving the turn unfinished.
- Record the health failure class (timeout, refused connection, invalid response), managed child exit code/signal, and runtime stderr so the initiating server failure can be diagnosed.
The current onOpenCodeRestarted event-stream rebind restores connectivity but does not make interrupted turns recoverable or visible.
Activity
openchamber-bot commentedon Aug 15, 2026
Thanks for the thorough report, @sum117 — you've mapped the failure path clearly: health failures accumulate while sessions stay busy, the stale-busy grace expires, OpenCode is force-restarted, the event stream rebinds (
onOpenCodeRestarted), but in-flight turns stay silently stranded with no aborted/interrupted state, notification, or retry action.Triaged as a bug in the sessions area (session lifecycle/status reconciliation after a forced managed-server restart) at high priority: it silently discards in-flight agent work in a core workflow, and it has recurred for you.
To help pin down root cause and repro:
[lifecycle]log)? That would confirm whether the process died, hung, or kept running but unresponsive before the forced restart.serve/web runtime? The managed lifecycle is shared server-side, so this decides whether the fix belongs in the web runtime or the Electron shell.Even just one incident window of logs would help. Thanks again for the detailed evidence.
sum117 commentedon Aug 15, 2026
Thanks. Answers below.
1. Managed OpenCode stderr / process state
No post-start managed stderr is available. In
createManagedOpenCodeServerProcess, OpenChamber attaches stdout/stderr listeners only while waiting for theopencode server listeningline;finish()removes both listeners once startup succeeds, and the returned process wrapper does not persist runtime stderr. The separate OpenCode log contains no fatal/OOM entry at either boundary.The lifecycle evidence points to a live-but-unhealthy child rather than a clean observed exit:
OpenCode process exited, restarting...branch. It counted failures through28/20, which meansisManagedOpenCodeProcessAlive()continued to return true.socket hang up, then withECONNREFUSED.Timed out waiting for OpenCode port <port> to be releasedbefore a new port was allocated.So the strongest conclusion is: the managed child stayed present while its server became unresponsive/stopped listening; the exact initiating exception is unavailable because runtime child stderr/exit diagnostics are not retained.
2. Runtime
Observed only in OpenChamber Desktop on Windows so far. I have not run a controlled reproduction using standalone
opencode serveor OpenChamber web. Desktop was launching the bundled child as:Therefore the OpenCode server wedge may reproduce standalone, but the silent forced-restart recovery behavior is confirmed only through the Desktop-managed lifecycle.
3. Stranded-session UI behavior
The observable behavior in both incidents was:
OpenCode logs show the old run ending with an active loop and the replacement run booting under a new run ID, with no continuation of the interrupted message. I can capture UI state plus process/runtime metrics on the next occurrence if that would help.
openchamber-bot commentedon Aug 15, 2026
Reproduced. I wired the session runtime and lifecycle runtime together exactly as
packages/web/server/index.jsdoes, drove the health-check failure threshold with 3 busy sessions, and confirmed that after the forced restart the busy sessions are silently stranded.Reproduction
Test:
packages/web/server/lib/opencode/reproduce-2943.test.json branchreproduce/issue-2943(passes — it asserts the buggy behavior).Steps reproduced:
session.status: busy(matching "OpenCode unhealthy with 3 busy session(s)").(1/20)→(20/20)).shouldSkipRestartForBusySessions()holds for the 2-minuteSTALE_BUSY_GRACE_MS.restartOpenCode()runs: old process closed, replacement spawned,onOpenCodeRestartedfires (therebindUpstream()rebind).After the restart, the following hold — the bug:
getActiveSessionCount()still returns 3 (all sessions remainbusyin the session runtime).getSessionStateSnapshot()/getSessionActivitySnapshot()still report all 3 asbusy.session.statusidle/aborted/error event was broadcast.openchamber:notificationwas broadcast.Root cause
onOpenCodeRestarted(index.js:1090) only callsmessageStreamRuntime.rebindUpstream(). The session runtime is never reconciled:resetAllSessionActivityToIdle()exists insession-runtime.js:284(and is exported + documented) but is never invoked anywhere in production code — only in its own unit test. So after a forced restart, the stalebusyphases/statuses survive indefinitely (until a later user message produces a new status transition), the active-session count stays elevated, and nothing emits a terminal event or notification for the interrupted turns.Additional gaps matching the issue's expected behavior (not reproduced in the test, verified by inspection):
restartOpenCodeonly setsstate.lastOpenCodeErrorfrom start-failure messages.killProcessOnPortis a no-op on Windows (lifecycle.js:57), so the restart is the only recovery path — making the missing session reconciliation more visible there.Likely fix location
packages/web/server/index.jsonOpenCodeRestarted(orpackages/web/server/lib/opencode/lifecycle.jsrestartOpenCode): reconcile sessions busy at restart time into an explicit interrupted/aborted state, emit terminal status, and broadcast a notification with retry/resend.packages/web/server/lib/opencode/session-runtime.js:resetAllSessionActivityToIdle()is the existing-but-unwired primitive.fix: settle busy sessions after managed OpenCode restart (openchamber…
fix: settle busy sessions after managed OpenCode restart (openchamber…
fix: settle busy sessions after managed OpenCode restart (openchamber…
fix: settle busy sessions after managed OpenCode restart (openchamber…