Repository navigation
Open
Labels
Description
Summary
During a routine auto-update service restart, every open TUI went to the "Connection lost / Reconnecting" screen, and one TUI crashed to the terminal with an unhandled transport defect. The root problem: the reconnect budget (3 attempts, ~16s) is much shorter than a realistic post-update election plus cold location boot (~45-60s observed), and when the budget is exhausted the ClientError escapes as a raw UnknownError stack dump instead of a clean exit.
ERROR (#1): UnknownError: An error occurred in Effect.tryPromise
at Tui.run (src/commands/handlers/default.ts:43:12)
at Tui.run (definition) (../tui/src/app.tsx:110:27) {
[cause]: ClientError: Transport
[cause]: Error: Unable to connect. Is the computer able to access the url?
}
Version: 0.0.0-next-15427, macOS arm64.
Timeline (from ~/.local/share/opencode/log/opencode.log, UTC)
| Time | Event |
|---|---|
| 14:56:29 | Elected server (run 5170d17b, still v15422) auto-updates the package on disk to v15427 (updated OpenCode ... method=bun) |
| 14:58:45 | User starts a new TUI (oc, run fc781962, launcher already v15427) |
| 14:58:46-47 | Old server tears down (mass watcher stopped, then silence): version-mismatch restart |
| 14:58:50-59:17 | Election herd: ~10 serve --service contenders spawn from open TUIs |
| 14:58:55-59:15 | Contender fb876f01 wins, logs role=server, cold-boots ~10 location graphs over 5-15s, then at 14:59:15 tears down again, displaced without ever serving the client |
| 14:59:01.331 | The new TUI exhausts 3 reconnect attempts and crashes with the unhandled error above |
| 14:59:31 | Second-wave contender db40851e (PID 5056) wins, registers, and serves healthily |
Problems
- TUI reconnect gives up too early and crashes ungracefully. 3 attempts / ~16s loses the race against any post-update restart. When it does give up, the failure should be a friendly "server unavailable" message and clean exit, not an escaped defect through
Effect.tryPromiseinTui.run(packages/cli/src/commands/handlers/default.ts:43,packages/tui/src/app.tsx:110). - Election double-bounce. The server restarted twice in one update cycle:
5170d17b->fb876f01(won, booted, torn down 20s later) ->db40851e. A second herd of contenders spawned whilefb876f01was still mid-boot and displaced it, doubling the outage window. Consistent with the known multi-holder service lock race. - Leaked contender process. A
serve --serviceprocess from the previous day (PID 91121, ~1GB RSS, parented by a TUI) was still alive and unregistered long after losing its election. Losing contenders should exit.
Expected
An auto-update restart should be invisible or near-invisible to open clients: at most a brief reconnecting state, one server bounce, no crashes, no leaked processes.
Reacted by Vlad Kosinov
Activity
holny commentedon Sep 2, 2026
I'd like to take this. The budget half is gone on
v2(indefinite retry), but the crash is still live — startup probes fail during a restart window and the ClientError escapesTui.rununhandled. Plan: route it through the existing CLI error channel with a clean exit. Election double-bounce is server-side, out of scope.holny commentedon Sep 2, 2026
PR #46726 is up with the fix — 4 required checks green. Only blocker: GitHub didn't pick up
Fixes #36688from the fork PR, so it needs a manual link in the PR's Development panel before it can merge. Everything else is ready for review. @kitlangtonholny commentedon Sep 9, 2026
Heads-up: the PR for this issue (#46726) was accidentally closed earlier today - its head fork got deleted unintentionally, which force-closed it. Re-filed as #48162 with the same commits. Sorry for the noise.
Bearmancer commentedon Oct 5, 2026
Windows / v2.0.23 datapoint: the benign branch of this same post-update race — one reconnect attempt, 6.8 s outage, no crash, no ERROR.
Mechanism (source verified against local clone,
dev@907b3bc)packages/client/src/generated/client.ts:167-168— every fetch throw is wrapped:ClientError(packages/client/src/generated/client-error.ts) doessuper(reason, options), somessage === "Transport"and the Bun/socket error rides incause. Whatever formats the log line or the TUI toast emitsTransport: <cause.message>— which is exactly the string I saw on screen:packages/cli/src/services/daemon.ts:110-135—start()reuses a running server only onfound?.version === InstallationVersion && compiled(line 114); otherwisestopProcess(found)(line 115) then spawn a new one. That is the version-mismatch restart, which my log records asreason=version-mismatch previousVersion=….packages/tui/src/context/sync.tsx:451-547—bootstrap()'s catch logstui bootstrap failed(line 541) and thenexit(e)whenfatal(lines 546-547). That is the crash half of this issue. On my machine that path did not fire.Timeline (verbatim from
C:\Users\Lance\.local\share\opencode\log\opencode.log, UTC, 2026-10-05)(
[…]= the multi-line stack frames of theInterruptErrorcause, elided; no other part of that line altered.)Two supporting observations from the same window:
60edadc9(firstserve --servicespawn) emits 13 lines, the last at08:38:37.377Z, then never logs again — noshutting downline — and it is not in the live process table. Run92b823b7(spawned08:38:48.521Z) is the service that serves today (service.jsonpid 13344). Cause of the first spawn's disappearance: not determinable from the log.08:38:25Zand08:40:46Zthere are zerolevel=ERRORlines. The first ERROR after the update is at08:40:47.458Z: 3×EPERM … rename 'session.json….tmp'(a separate mechanism — atomic-write rename collisions, not transport).Expect vs Actual
Reconnectingblip, no crash, no escapedClientErrordefect.08:38:40.254Z, one failed attempt at08:38:42.258Z, connected at08:38:49.042Z→ 6.78 s outage, 1 attempt, notui bootstrap failed, noUnknownErrorstack, no ERROR line. The client-side screen text I saw at startup wasTransport: The socket connection was closed unexpectedly…(see hygiene note below).Repro (observed, not reproducible on demand)
updated OpenCode from=2.0.22 to=2.0.23 method=bun.cli starting version=2.0.23).background service starting reason=version-mismatch previousVersion=2.0.22→ old server tears down (InterruptErroron/api/event) → newserve --servicespawns.error="Timed out connecting to server", or on other runserror="Transport: The socket connection was closed unexpectedly…").Counter-scenarios tried
message="event stream disconnected"line was re-paired with the samerun='s nextevent stream connected(log starts 2026-09-27T22:39:12.657Z; all counted events are 2026-09-28 or later)level=INFO; 31 reconnected within 0.1–9.3 s; 1 (d6f9fff6, 09-28T06:40:17Z) re-disconnected 7 s later and then reconnected 9.3 s after that; 1 (090ea0c4, 10-01T05:41:22Z) has no reconnect because that line is the run's last log line — process exitGet-CimInstance Win32_Process -Filter "Name='opencode.exe'"(PID, PPID, CreationDate, CommandLine).bun\bin\opencode.exe(08:38:36Z), 21136 its child, no args (08:38:36Z, = run6d168471, confirmed by thesession.json.21136.*.tmpnames in the 08:40:47 EPERM lines), 13344…\@opencode\cli\bin\opencode.exe serve --service(08:38:48Z, =service.jsonpid)~/.local/state/opencode/service.json{"id":"45b432f6-…","version":"2.0.23","url":"http://127.0.0.1:49374","pid":13344,"password":"<redacted>"}Get-ChildItem ~\.local\state\opencode -Filter *.tmp -Recurse.tmplitter from the EPERM renames is still around"level=ERRORbetween08:38:25Zand08:40:46Z08:40:47.458-459Z)bootstrap()took theexit(e)path at this update" — corroborated by 0 genuine hits fortui bootstrap failedandUnknownErroras application log lines (the only whole-log matches are our own grep commands echoed back by the server)Evidence hygiene (what I am not claiming)
Transport: The socket connection was closed unexpectedly…does not appear inopencode.logon 2026-10-05. The log's record of that startup failure readserror="Timed out connecting to server"(quoted above); the on-screenTransport: …text is user-observed in the TUI, and its provenance from a specific call at 08:38Z is UNVERIFIED. What is verified: that exact string is logged 17× as an event-streamerror=value, and once as aClientError: Transport: … (cause: TypeError: …)unhandled rejection in arole=cliprocess.event stream disconnectedandbackground service starting … reason=version-mismatchwere not located in my local clone (dev@907b3bc) — the strings are verified from the shipped 2.0.23 binary's output only. The version-gated restart logic they describe is verified atdaemon.ts:110-135.Environment
Why this helps this issue
This is the good outcome of the same race this issue reports the bad outcome of: same trigger (version-mismatch restart after auto-update), same
ClientError("Transport")surface, and on this machine the reconnect budget was never exhausted, sobootstrap()never reached itsexit(e). It brackets severity for #36688: the failure is not "Windows always crashes", it is "whoever loses the ~7 s race crashes — and this run won". It also shows the same double-spawn shape as the issue's problem #2 (twoserve --servicespawns 11.8 s apart in one update cycle, the first one's log trail ending with no shutdown line).vladkosinov commentedon Oct 26, 2026
Seen on Windows 11 (TUI 2.0.27, Desktop app 2.0.32). Trigger here is the version-mismatch replace from #55429: the TUI starts its own service while the Desktop app restarts its own, the TUI's startup probe fails, and it exits with the same unhandled error:
The fix PR #48162 was auto-closed on 9 Oct without a review. Could it be reopened?
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I reproduced the ungraceful crash, both on
@opencode/[email protected](0.0.0-next-15427isn't on npm) and on the latestdev(77a0fd6).How: I ran
opencode servebehind a small proxy that answers only the CLI's first/api/infohealth check and then stops listening. This simulates the server going away right after the client finds it, as happens during a post-update restart. Then I ranopencode --server <proxy>.Result: the TUI exits within a second and prints the same raw stack as in the report:
The failing call is the TUI's first location request in
Tui.run. It has no retry and no friendly error handling. The script below reproduces it.Not covered: I couldn't reproduce the managed-service reconnect budget (
--serverskips that path), the election double-bounce, or the leakedserve --servicecontender. Those need a real multi-process post-update restart.Reproduction script (repro.sh)
Run it:
bash repro.sh