Skip to content

cli(win): 45s event-stream idle watchdog restarts the managed service, aborting all sessions/subagents #52049

Open

Description

@wurenrumian

Description

On Windows the v2 managed background service (opencode serve --service) is repeatedly killed and respawned during normal use, aborting every in-flight session and background subagent ("failed"). It is not a crash — the client is killing it.

Every restart is preceded by the client log line event stream disconnected ... error="Event stream stalled", then ~6s later a new cli starting ... args=["serve","--service"].

The direct trigger is the client's event-stream idle watchdog (values read from the shipped 2.0.19 binary):

  • server sends : heartbeat on /api/event every 15s;
  • client resets a 45s idle timer on any received byte (idleTimeout ?? 45000);
  • after 45s with no bytes the client aborts with Event stream stalled, then treats the service as unresponsive and replaces it (Background service is unresponsive; recovery cannot preserve persistent terminals, Failed to shut down persistent terminals before service replacement) instead of reconnecting.

Measured correlation: server's last log line 08:47:12.749, stall logged 08:48:40.688 → 46.1s, i.e. the 45s watchdog, not the ~6s health probe.

This is the same class as #50594 (Windows: file-watcher re-registration storm on ~/.claude/skills → serve --service disappears with no error/dump) and #50591 (Linux: Event stream stalled + InterruptError on /api/event). The new information here is that the service is not dying on its own: the client kills it after a 45s heartbeat gap, so the "silent death" in #50594 is a client-side replacement, and the fix must also cover the watchdog margin / the replace-instead-of-reconnect policy.

Ruled out on this machine:

  • Not a crash — no opencode.exe APPCRASH in the Windows Application log; no crash dumps under %LOCALAPPDATA%\CrashDumps or WER (no RADAR_PRE_LEAK_64 either).
  • Not OOM — 23.7 GB RAM, 6.7 GB free, page file usage ~205 MB.
  • Not graceful shutdown — 2 shutting down lines in 12 days; the old process logs nothing before disappearing.
  • Not the subagents — they are victims of the replacement.

Frequency (opencode.log, one machine): Event stream stalled = 3× on 2026-09-28, 6× on 2026-09-29; serve --service starts = 7 and 6 on those days. It stops completely when idle (12+ min with zero restarts, liveness probe 0–2ms).

Suspected trigger for the ≥45s heartbeat gap (evidence below, hypothesis): concurrent cold-boot of many locations plus watcher re-registration churn, forming a feedback loop — replace service → all recently-active locations cold-boot → watcher/config/MCP churn blocks the loop → heartbeat gap ≥45s → watchdog replaces the service again.

Plugins

  • local ~/.config/opencode/plugins/orca-opencode-status.js
  • local ~/.config/opencode/plugins/win-notify/
  • no plugin array in opencode.json

(~/.claude/skills and ~/.agents/skills are both 44-entry directories and are re-subscribed ~950× each — see Additional context.)

OpenCode version

2.0.19 (channel=latest, npm global @opencode/cli)

Steps to reproduce

Intermittent, load-dependent; not reproducible on demand.

  1. On Windows with opencode 2.0.19 CLI/TUI, let the shared managed service run (~/.local/state/opencode/service.json → http://127.0.0.1:49372).

  2. Have several locations active (home directory plus several projects on other drives) and run subagent-heavy turns.

  3. Observe repeatedly in ~/.local/share/opencode/log/opencode.log:

    08:48:40  client  event stream disconnected ... error="Event stream stalled"
    08:48:46  cli     cli starting version=2.0.19 args=["serve","--service"]
    08:48:47  client  event stream connected
    
  4. In-flight work is aborted: InterruptError: All fibers interrupted without error ... http.url=/api/event http.status=200.

  5. Restarts recur every ~2.5–16 min while busy; zero restarts while idle.

Screenshot and/or share link

N/A — log excerpts, code snippets and counts are inline below.

Operating System

Windows 11 Home Single Language, build 26200

Terminal

Windows Terminal (WT_SESSION set, TERM=xterm-256color), shell C:\WINDOWS\system32\cmd.exe

Additional context

Relevant client code (from the shipped 2.0.19 binary):

// event stream idle watchdog
var yY = 2000, Pv = 1000, gY = 50, bY = 45000, EY = 20000;
function wv(t, n) {
  let u = n.idleTimeout ?? bY;              // 45000 ms
  ...
  let Ke = () => { x = Date.now(); ...; qe = setTimeout(() => te.abort(Error("Event stream stalled")), u) };
  // onActivity resets the timer on ANY received byte
}
// server SSE handler
let s = qK("15 seconds").pipe(wa(() => `: heartbeat\n\n`));
// SSE reader: any byte counts as activity, including the heartbeat comment
if (!y.done) n?.onActivity?.();

Watcher churn / location cold-boot evidence (hypothesis for the 45s gap):

stall 08:18:06 : watcher_subscribe=116  location_boot=8   spawns=40
stall 08:24:19 : watcher_subscribe=123  location_boot=9   spawns=12
stall 08:26:55 : watcher_subscribe=129  location_boot=10  spawns=6
path subscribes
C:\Users\<user>\.claude\skills 944
C:\Users\<user>\.agents\skills 923
C:\Users\<user>\.config\opencode\skills 250
C:\Users\<user> 162
C:\, C:\Users, D:\ (type=entries) 77 / 77 / 80

location services booted: C:\Users\<user> 73×, plus ~20 other project locations. This matches #50594 (.claude/skills storm) and #51852 (intermittent main-thread spin correlated with watcher churn).

Next step being tested: OPENCODE_DISABLE_FILEWATCHER=true (per #51852). Will report whether restarts stop under the same load.

Related issues: #50594, #50591, #51852, #51216, #37795, #48588, #36285.

Activity

  1. github-actions commented on Sep 29, 2026

    @github-actions
    Contributor

    This issue doesn’t fully meet our contributing guidelines.

    What needs to be fixed:

    • This is a bug report, but it does not include the required Description field from the Bug Report template (the body uses Summary). Please update the issue using the Bug Report template and provide the description under Description.

    Please edit this issue to address the above within 2 hours, or it will be automatically closed.


    This issue might be a duplicate of existing issues. Please check:

    • #50591: reports the same Event stream stalled symptom followed by a managed-service restart, though on Linux.
    • #51852: reports intermittent managed-service stalls/restarts correlated with file-watcher churn.
    • #50594: reports Windows file-watcher churn terminating serve --service and aborting active sessions.

    If you believe this was flagged incorrectly, please let a maintainer know.

  2. github-actions commented on Sep 29, 2026

    @github-actions
    Contributor

    Thanks for updating the issue with the Bug Report details and system information!

  3. piknockyou commented on Sep 30, 2026

    @piknockyou

    Had the same failure: the session title gets created, but no reply ever comes - only the server restarted while you were working notice, with event stream disconnected / stalled in the log.

    Root cause for me: a stray empty .git folder in my home folder. That hidden folder makes everything around it look like one big "git project" - so opencode treated my entire home folder as the project and scanned all of it for changes before each reply (541k files, ~26 seconds per scan, twice per message). That is longer than opencode's internal watchdog waits, so it assumes the service died, restarts it, and you get the loop.

    Quick check - run this in the folder where you start opencode:

    git rev-parse --show-toplevel

    • If it prints your home folder (or a big folder above the project you actually work in), that leftover .git is the problem. Delete that .git folder (accidents like this usually contain no commits, so nothing is lost - if unsure, just rename it to .git.bak). Then start opencode inside a normal project folder, and never run git init in your home folder. This removed the failure completely for me.
    • If it prints your real project folder, the folder is fine and the files inside are the problem. Run:
      git ls-files --others --exclude-standard | wc -l
      If that number is huge (100k+), opencode is choking on files that should have been skipped. List your big folders that don't belong to the project (node_modules/, target/, build/, caches, datasets) in a .gitignore file - one per line. As a last resort you can set "snapshot": false in your opencode config, which switches the scan off (but you also lose the undo feature).

    Deleting the stray ~/.git / %USERPROFILE%\.git was enough for me.

  4. ptmr3 commented on Oct 3, 2026

    @ptmr3

    Reproducing this on macOS (Apple Silicon) with an additional deterministic trigger: the OpenCode desktop app running concurrently.

    Versions: CLI v2.0.21 and v2.0.22 (identical behavior on both; the updater installed 2.0.22 mid-incident); desktop app v1.18.34.

    Evidence (from ~/.local/share/opencode/log/opencode.log):

    • 15 Event stream stalled events on 2026-10-02; zero on each of the four prior days — the signature appeared for the first time with this setup
    • 15 serve --service spawns in ~9 minutes, one per ~57s; the cadence is machine-regular: connect → exactly ~46s → Event stream disconnected … error="Event stream stalled" → ~11.5s → reconnect to a freshly spawned service (9 consecutive cycles)
    • Same recovery cannot preserve persistent terminals line at each kill
    • Desktop-app interaction: the desktop server's own global-event stream disconnects 22 seconds after the first CLI service spawns (global event disconnected) and stays down for the entire churn; it reconnects only after the CLI processes are killed. The loop did not occur in a CLI-only setup (desktop app quit).
    • User sessions never materialize server-side during the churn — session IDs receive 404s from /api/session/<id>/permission|form, zero sessions created — so the CLI is effectively unusable alongside the desktop app.

    Workaround: don't run the CLI TUI while the desktop app is running.

    Happy to provide the full log window or any additional diagnostics from the macOS side.

  5. ptmr3 commented on Oct 3, 2026

    @ptmr3

    Follow-up from our macOS reproduction with protocol-level evidence — this isolates the root cause precisely:

    The service emits no SSE heartbeats at all. A direct probe (curl -N --noproxy '*' http://127.0.0.1:<port>/api/event — no proxy, no interceptors, direct socket) received zero bytes for 42+ seconds — not even HTTP response headers — on an idle stream. The only emissions observed are boot-burst events (provider/model/agent/command.updated) and activity events; there is no keepalive.

    The watchdog is exactly 60.0s. Measured across multiple cycles: connect → exactly 60.00s of silence → Event stream stalled → the client kills the server child (PIDs observed cycling) → respawn (~11.5s) → reconnect → repeat indefinitely. Every kill logs InterruptError: All fibers interrupted at ServerProcess.start server-side.

    Environment fully excluded: no proxy env vars at any layer, no SSE-intercepting software installed, and the TUI stalls identically under a scrubbed env -i environment as under the full user shell config.

    Cross-client confirmation: a second, independent client (a long-lived session through a different service instance) stalls the same way in every >60s idle window — 30 stalls across 7 runs. The failure is not specific to one TUI; any idle consumer trips it.

    Nuance worth noting: the CLI works while ACTIVE — non-interactive opencode run completes fine, sessions create, activity events flow. It is specifically idleness that kills it: for an interactive session, 60s of reading/thinking = service killed + respawn loop.

    Session orphaning: the replace-not-reconnect policy also orphans sessions — we observed sessions created against one instance 404ing ~0.9s later because the replacement instance answered. User-typed prompts can vanish this way.

    Suggested fix scope: (1) the service should emit SSE keepalives (e.g., : ping every 15-30s) on /api/event; (2) the client's idle watchdog should not treat a silent stream as fatal; and critically (3) the replace-not-reconnect policy should not kill the server child on reconnect.

    Reproduced on v2.0.22 (latest; brew anomalyco/tap/opencode-v2), macOS Apple Silicon.

  6. Bearmancer commented on Oct 5, 2026

    @Bearmancer

    Second Windows box (Win10 19045, v2.0.23): 8-day census — stall → service replacement occurs here too, but it is rare (3 stalls / 8 days) and the other branch (stall → plain reconnect) is also observable on the same machine.

    Different machine from the report above (Windows 10 Pro build 19045 vs your Windows 11 build 26200; 2.0.23 vs 2.0.19), so this is an independent datapoint, not a reproduction of your counts.

    Evidence (verbatim, C:\Users\Lance\.local\share\opencode\log\opencode.log, UTC)

    Every Event stream stalled this machine logged on or after 2026-09-28 (the log starts 2026-09-27T22:39:12.657Z) — 3 total, all level=INFO:

    timestamp=2026-09-28T06:41:19.291Z level=INFO run=d6f9fff6 message="event stream disconnected" component=client attempt=1 error="Event stream stalled" role=cli
    timestamp=2026-09-30T07:35:55.630Z level=INFO run=b0336b3d message="event stream disconnected" component=client attempt=1 error="Event stream stalled" role=cli
    timestamp=2026-09-30T07:37:01.835Z level=INFO run=b0336b3d message="event stream disconnected" component=client attempt=1 error="Event stream stalled" role=cli
    

    What followed each one:

    stall (UTC) same-run reconnect new serve --service spawn delta
    2026-09-28 06:41:19.291 6.7 s later 06:41:25.562Z run=b0a22f04 +6.3 s → replacement branch
    2026-09-30 07:35:55.630 1.3 s later none (next spawn is 07:37:08) plain reconnect branch
    2026-09-30 07:37:01.835 8.9 s later 07:37:08.777Z run=d4ebd344 +7.0 s → replacement branch
    timestamp=2026-09-28T06:41:25.562Z level=INFO run=b0a22f04 message="cli starting" version=2.0.18 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-09-30T07:37:08.777Z level=INFO run=d4ebd344 message="cli starting" version=2.0.20 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    

    Your client-side warning string does occur on this machine (2×, both level=WARN, both role=cli):

    timestamp=2026-10-01T07:10:01.602Z level=WARN run=efd63e99 message="Background service is unresponsive; recovery cannot preserve persistent terminals" role=cli
    timestamp=2026-10-02T12:45:53.024Z level=WARN run=66a81987 message="Background service is unresponsive; recovery cannot preserve persistent terminals" role=cli
    

    Preceding context of the 10-01 warning — competing spawns, then a port collision:

    timestamp=2026-10-01T07:09:51.309Z level=INFO run=34331466 message="Sent HTTP response" http.span=188 role=server http.method=GET http.url=/api/info http.status=503
    timestamp=2026-10-01T07:09:57.795Z level=INFO run=f91b52c6 message="cli starting" version=2.0.21 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-10-01T07:10:01.602Z level=WARN run=efd63e99 message="Background service is unresponsive; recovery cannot preserve persistent terminals" role=cli
    timestamp=2026-10-01T07:10:06.062Z level=INFO run=f9cc67d9 message="cli starting" version=2.0.21 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-10-01T07:10:19.636Z level=ERROR run=f91b52c6 message="cli process failed" cause="Cause([Fail(Error: Managed service port 49374 on 127.0.0.1 is already in use by another process. Configure another port with `opencode service set port <port>` and […])"
    

    Replacement also happens without a stall. In the 2.0.22→2.0.23 update window on 2026-10-05 the client failed on connect, not on idle, and a new service was still spawned 6 s later:

    timestamp=2026-10-05T08:38:36.700Z level=INFO run=60edadc9 message="cli starting" version=2.0.23 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-10-05T08:38:40.254Z level=INFO run=6d168471 message="event stream connecting" component=client attempt=0 role=cli
    timestamp=2026-10-05T08:38:42.258Z level=INFO run=6d168471 message="event stream disconnected" component=client attempt=1 error="Timed out connecting to server" role=cli
    timestamp=2026-10-05T08:38:48.521Z level=INFO run=92b823b7 message="cli starting" version=2.0.23 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-10-05T08:38:49.042Z level=INFO run=6d168471 message="event stream connected" component=client role=cli
    

    Run 60edadc9's log trail ends at 08:38:37.377Z with no shutdown line and no such process in the live process table; 92b823b7 is the survivor (service.json pid 13344). Cause of the first spawn's death: not determinable from the log.

    Frequency (my log, my counting method)

    Service spawns = lines matching message="cli starting" … args=["serve","--service"], counted per UTC day:

    date spawns date spawns
    2026-09-28 8 2026-10-02 10
    2026-09-29 15 2026-10-03 1
    2026-09-30 6 2026-10-04 12
    2026-10-01 13 2026-10-05 4

    Total 69 service spawns in 8 days, versus only 3 Event stream stalled events. So on this machine the 45 s watchdog stall is not the dominant spawn driver — most spawns come from version-mismatch restarts (5 auto-updates in that window: 2.0.18→2.0.19→2.0.20→2.0.21→2.0.22→2.0.23) and from the competing-spawn/port-collision pattern shown above. Zero stalls on 2026-10-04 and 2026-10-05 despite 12 and 4 spawns respectively.

    Full disconnect census (log starts 2026-09-27T22:39:12.657Z; all counted events are 2026-09-28 or later) (message="event stream disconnected", 33 lines, all level=INFO):

    error count
    Transport: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch() 17
    Timed out connecting to server 13
    Event stream stalled 3

    31 of the 33 reconnected for the same run= within 0.1–9.3 s; 1 re-disconnected and then reconnected; 1 is the last line of its run (process exit). Crash checks: no disconnect has a level=ERROR line as its next line, or within the next 5 lines (0 of 33); the first ERROR after the 10-05 update window is the 3× EPERM block 2 minutes after its disconnect (cited in counter-row 5); tui bootstrap failed never appears as an application log line (the only whole-log matches are our own grep commands echoed back); UnknownError never appears except inside my own grep commands echoed back by the server; the Windows Application log holds 78 Application Error (provider Application Error, ID 1000) events in the last 8 days, 0 of them mention opencode.

    Expect vs Actual

    • Expect (per your report): after ≥45 s without a byte the client aborts the stream and replaces the service, aborting in-flight work, repeatedly while busy.
    • Actual here: that branch exists — 2 of 3 stalls were followed by a new serve --service 6.3 s and 7.0 s later, and the Background service is unresponsive; recovery cannot preserve persistent terminals warning fired twice — but on this machine it is rare (3 stalls / 8 days), one stall recovered by plain reconnect in 1.3 s, and the far more common spawn source is version-mismatch restarts plus competing spawns that collide on the managed port.

    Steps to reproduce (observed, not reproducible on demand)

    1. Windows 10, opencode 2.0.23, shared managed service on http://127.0.0.1:49374.
    2. Run subagent-heavy work across several locations for days.
    3. Wait for an idle gap ≥45 s on /api/event (heartbeat interval 15 s per your analysis) — happens ~3 times per 8 days here.
    4. Read ~/.local/share/opencode/log/opencode.log for error="Event stream stalled" and, 6-7 s later, args=["serve","--service"].
    5. In-flight work in that window is interrupted; the old server's /api/event handler dies with InterruptError: All fibers interrupted without error … http.method=GET http.url=/api/event http.status=200 (example from the 2.0.23 update: timestamp=2026-10-05T08:38:28.433Z level=INFO run=38b70a8a cause="InterruptError: All fibers interrupted without error […] http.method=GET http.url=/api/event http.status=200).

    Counter-scenarios tried

    # Check (how) Result Rules out
    1 Re-pair all 33 event stream disconnected lines with the same run='s next event stream connected all 33 level=INFO; 31 reconnected in 0.1–9.3 s; 1 re-disconnected then reconnected; 1 = final line of its run (process exit) "these disconnects are crashes / dead services"; "the stream never comes back"
    2 Get-CimInstance Win32_Process -Filter "Name='opencode.exe'" (PID/PPID/CreationDate/CommandLine) 3 processes: 19636 (08:38:36Z), 21136 (08:38:36Z), 13344 serve --service (08:38:48Z) "a lingering pre-update (2.0.22) daemon is being blamed for the 2.0.23 failures" — everything alive started after the 08:38:25Z update line
    3 ~/.local/state/opencode/service.json single registration {"id":"45b432f6-…","version":"2.0.23","url":"http://127.0.0.1:49374","pid":13344,"password":"<redacted>"} "duplicate/stale registration entries causing pointless respawns"
    4 Get-ChildItem ~\.local\state\opencode -Filter *.tmp -Recurse 0 files "leftover .tmp files from interrupted atomic writes are wedging the registration"
    5 level=ERROR in 08:38:25Z–08:40:46Z 0 lines (first ERROR after the update is 3× EPERM … rename 'session.json…​.tmp' at 08:40:47.458-459Z, a separate mechanism, not transport) "the update-window failure on this box was a crash rather than a reconnect"

    Environment

    Caption        : Microsoft Windows 10 Pro
    Version        : 10.0.19045
    BuildNumber    : 19045
    OSArchitecture : 64-bit
    
    opencode v2.0.23
    bun 1.4.2
    gh version 2.102.0 (2026-09-30)
    git version 2.55.0.windows.5
    

    Scope note

    This does not contradict your measurement — different machine, different load, newer version. What it adds: (a) the stall→replace branch reproduces on a second Windows box with your exact warning string; (b) the same box also shows the benign branch (stall → 1.3 s reconnect, no replacement) and a non-stall replacement path (connect timeout → new spawn); (c) stall counts alone under-count replacements here, because version-mismatch restarts and competing spawns on the managed port dominate the 69 spawns/8 days.

  7. Brams-s commented on Oct 8, 2026

    @Brams-s

    Linux datapoint: same stall → replace path. Here the trigger is host CPU saturation, and the replacement makes things worse.

    Environment: Linux 6.8 (Ubuntu), x86_64, 24 cores / 64 GB. @opencode/cli 2.0.24 daily, with one managed serve --service shared by about 10–19 TUI clients (one per Herdr pane) plus subagents. Plugin: oh-my-opencode-slim 3.0.3. The file watcher is disabled (OPENCODE_DISABLE_FILEWATCHER=1), so the watcher storms from #50594 / #53416 are ruled out here.

    What happens

    • When the host is heavily loaded (load average 90–160 on 24 cores, from browser-automation and test workloads), the shared server stops logging entirely, then every client logs event stream disconnected ... error="Event stream stalled" (some also Timed out connecting to server).
    • In the bad case, a client then logs Background service is unresponsive; recovery cannot preserve persistent terminals. The old server is SIGKILLed: a systemd user unit supervising it recorded Main process exited, code=killed, status=9/KILL, with no kernel OOM-killer entry. A new serve --service is started, and every in-flight turn and subagent in every session is aborted.
    • Silence before the stall: the server's last log line came 77 s and 81 s before the first Event stream stalled in two replacements I measured, consistent with the 45 s idle watchdog in this issue. These are freezes, not crashes: Linux PSI showed memory full avg300≈9% and very high CPU run queues at the time.

    Frequency, over two days on one machine

    27 stall bursts, each with 1–34 clients logging the stall within the same minute:

    • About 7 ended in replacement: Oct 7 17:04 and 18:17; Oct 8 17:15, 17:33, 19:17, 20:37 and 20:39 (UTC).
    • The rest recovered by plain reconnection.

    Why replacing makes it worse here

    1. The new server starves just like the old one. The cause is the host, not a wedged process. Replacement only adds a cold start: plugin loading, MCP reconnects and session drains. Meanwhile it aborts every session's work.
    2. Thundering herd. Every client detects the stall independently. On Oct 7 at 18:17:24–27, 14 clients each ran serve --service within ~2 s. New servers answered /api/info with 503 while booting. On Oct 7 at 17:05, two servers came up concurrently and one shut down after losing the port race.
    3. Supervision gets overridden. Running the server under a systemd user unit (Restart=on-failure, crash output to the journal) doesn't help: the client SIGKILLs the supervised process and starts its own unsupervised replacement in the client's cgroup. This is the same issue as service: managed serve stays inside the spawning client's cgroup — client memory limits starve the shared server into watchdog kill loops #48588.

    Suggestions (in addition to the watchdog margin and heartbeat points raised above)

    • Single-flight replacement: one host-wide lock or lease so only one client may replace the service. The others wait and reattach.
    • Check that the process is alive before replacing. If the recorded PID exists and isn't a zombie (e.g. its /proc/<pid>/stat state is R/S/D), back off and reconnect instead of SIGKILLing. Better still, keep the heartbeat on a timer that a busy event loop can't starve.
    • Make the stall threshold configurable (env or config), so heavily loaded hosts can trade detection speed for not losing every session.
    • Respect an externally managed service. If the service was not spawned by a client (e.g. a systemd unit), don't kill it; report and wait.

    Happy to provide log excerpts or run a build with extra logging.

  8. luhfilho commented on Oct 11, 2026

    @luhfilho

    Confirming on macOS and adding the multi-client angle, which makes the restart loop much worse.

    Environment: opencode 2.0.26, macOS (Darwin 27.0.0, arm64), iTerm.app, zsh. No plugins. ~/.opencode/bin/opencode, channel latest.

    Same client-side replacement. Three TUIs were attached to the one shared serve --service. When the daemon's event loop ran long (see aggravators below), all three declared the stream stalled in the same millisecond, then all three spawned serve --service within the same second — a restart stampede on a single port:

    timestamp=2026-10-11T20:28:17.233Z level=INFO run=6567608a message="event stream disconnected" component=client attempt=1 error="Event stream stalled" role=cli
    timestamp=2026-10-11T20:28:17.233Z level=INFO run=71eedb1d message="event stream disconnected" component=client attempt=1 error="Event stream stalled" role=cli
    timestamp=2026-10-11T20:28:17.233Z level=INFO run=70466011 message="event stream disconnected" component=client attempt=1 error="Event stream stalled" role=cli
    timestamp=2026-10-11T20:28:28.496Z level=INFO run=6cc1c221 message="cli starting" version=2.0.26 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-10-11T20:28:28.498Z level=INFO run=9dfbd64d message="cli starting" version=2.0.26 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    timestamp=2026-10-11T20:28:28.508Z level=INFO run=a2e8b716 message="cli starting" version=2.0.26 channel=latest local=false args="[\"serve\",\"--service\"]" role=cli
    

    Stalls/restarts repeated every 1–3 minutes (19:33, 19:43, 19:49, 20:05, 20:09, 20:17, 20:28, 20:31, 20:34, 20:36, 20:39 …), each stall followed ~11 s later by a fresh serve --service. Same class as #50591/#50594, and overlapping #54181 ("restart stampede races the service slot").

    Also on macOS: service set|unset recycle the live daemon. Besides serve --service, opencode service restart|set|unset are forwarded to the real binary and go through ~/.config/work-managed port 49374, so a config write (service set env …) also kills the shared daemon and drops every session. Only special-casing serve --service while ignoring service set|unset is not enough.

    Aggravators on this machine (not the root cause, but what made the loop constant):

    • ~/.local/share/opencode/opencode.db was 4.5 GB (part 1.6 GB + session_message 1.6 GB), WAL checkpoint 2.1 GB / 246 s — the daemon pegged one core for minutes. After the DB was migrated/compacted to 1.7 GB the pressure dropped.
    • A local MCP server (peekaboo) whose initialize/tools/list took ~30 s and timed out (mcp connect failed server=peekaboo … Request timed out, http.span=30899), re-attempted on every restart.

    Workaround used: a shim named opencode that for serve --service adopts an existing listener on the port (holds the client open) instead of starting a second daemon, and that refuses service restart|stop|start|set|unset while a daemon is alive. With it the daemon survived 20+ minutes instead of being replaced every 1–3 minutes. This only masks the client behaviour — the real fix is on the client side.

    Suggestion: on Event stream stalled, retry/reconnect the SSE stream (or fall back to polling/refetch) and only start a service when nothing is listening on the port yet. Ideally also treat service set|unset as non-destructive config writes that never recycle the running service.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions