Repository navigation
desktop(win): background service SIGKILLed mid-turn when snapshot capture blocks /api/info for >6s -> ~60s restart loop, aborted turns #51216
Description
Description
On Windows, the Desktop app kills its own background service while the service is
legitimately busy, which aborts the running turn and puts the app into a restart loop.
When a session's checkpoint work keeps the CLI's HTTP server busy for more than ~6 s,
the Desktop main process declares the background service "unresponsive", stops it
(SIGTERM -> SIGKILL) and spawns a new one. Every in-flight request is aborted
(HTTP 499), the running turn is interrupted, the app resumes it, the same work runs
again, and the cycle repeats roughly every 60 seconds.
User-visible result: the session is unusable. The activity list fills up with
"restart continuation" entries, background subagents end up "failed", and the app
never finishes a turn. In one day the CLI was started 111 times (vs. 3-6 starts/day
before the same app version), with 150 InterruptErrors.
The trigger in my case was the snapshot/checkpoint path:
timestamp=... level=INFO message=session.revert.stage sessionID=<session> messageID=<message> files=undefined http.span=862
timestamp=... cause="InterruptError: All fibers interrupted without error
at Git.index.refresh (chunk-g2m6xm1v.js:24:2279)
at Git.tree.capture (chunk-xm1wvpce.js:121:2381)
at Snapshot.capture (chunk-xm1wvpce.js:121:4527)
at SessionRevert.stage (chunk-xm1wvpce.js:137:1835)
at Session.revert.stage (chunk-t9es9kf6.js:442:10196)"
http.span=46646 role=server http.method=POST http.url=/api/session/<session>/revert/stage http.status=499
So POST /api/session/<id>/revert/stage ran for 46.6 s, blocked the CLI process, and
was then aborted by the app (499 = client went away, i.e. the service was killed).
While that capture was running I could verify the consequences directly:
netstat/raw TCP connect to the registered service URL succeeded (the port was
listening), butGET /api/infonever answered — repeated 8 s timeouts.- After the automatic replacement, the same endpoint answered
200 {"version":"2.0.16","pid":<pid>,"urls":["http://127.0.0.1:<port>"],...}in ~5 ms. - Same request again after reducing the work: 46.6 s -> 1.22 s (HTTP 200).
The Desktop main log shows the loop (trimmed, repeated every ~59 s for hours):
[17:26:18.018] [info] (main) starting v2 background service
[17:26:24.103] [info] (main) v2 CLI background service starting { reason: 'missing' }
[17:26:24.609] [info] (main) v2 CLI background service ready { url: 'http://127.0.0.1:<port>', ... }
Relevant behaviour in the packaged app (resources/app.asar -> out/main/service-*.js):
{ pollInterval: 25, requestTimeout: 2e3, spawnDelay: 5e3, maxSpawnDelay: 3e4,
promiseTimeout: 12e4, stopPollInterval: 50, stopPollAttempts: 100 }- the health probe is
GET /api/infowith a 2 s timeout; - after 3 consecutive timeouts the service is declared unresponsive and
replaced (SIGTERM, then SIGKILL,service.jsondeleted); - the next
ensure()then reportsreason: 'missing'and spawns a new CLI; /api/inforeturning 503 is only ever treated asstate: 'waiting'.
In other words: a backend that is busy doing real work for >6 s is treated as dead,
and the kill takes the running session down with it.
Expected Behavior
- A busy backend should not be considered dead after ~6 s. The health budget should
be much larger and/or adaptive, and a service that is responding on its socket
should not be SIGKILLed while a request is in flight. - Killing/replacing the service should not silently abort running turns; the
session state should survive a service replacement. Snapshot.captureshould not block the HTTP server (see core: session never drains when Snapshot.capture() stalls on huge untracked tree — git add hangs, execution claim never released #48657 for the capture
stall itself). At minimum, health/liveness endpoints should not be blocked by
filesystem/git work.
Steps to reproduce
- On Windows, use a git project whose untracked, non-ignored tree is large
(in my case: a repo created by another tool with a single empty commit, no
.gitignore, 76,921 untracked/non-ignored files, ~11.8 GB, 76,458 of them
undernode_modules/). - Open a session in that directory in the Desktop app and run a turn long enough
that the app stages a checkpoint (POST /api/session/<id>/revert/stage). - The request runs for tens of seconds;
/api/infostops answering; the Desktop
kills and restarts the CLI; the turn is aborted ("restart continuation"); repeat.
Additional Context
- Interrupted endpoints in my logs:
GET /api/event(156x, the session event
stream),POST /api/rpc/experimental.browser/attach(9x),GET /api/vcs/diff
(7x),POST /api/session/<id>/revert/stage(4x),POST /api/worktree/refresh(2x). - Workaround that stopped the loop immediately: add ignore rules for build
artifacts (node_modules/,target/,.venv/,dist/,build/, caches) to the
repo (both.gitignoreand the snapshot view — note the snapshot repo uses its own
git dir). Capture time dropped from 46.6 s to ~1.2 s and the service has now been
running for hours without being replaced. - Two secondary problems found in the same snapshot code, both worth hardening:
- a file named
nulin the work tree makes every capture fail with
error: invalid path 'nul'/error: unable to add 'nul' to index/
fatal: adding files failed(reserved device name on Windows; created by a
Linux-side tool) — 10+ failures per minute while a turn was running; - a stale
index.lockleft in the snapshot repo caused permanent
failed to capture snapshotfailures (331 occurrences over two days) with no
self-healing (see snapshot: corrupt index causes repeated capture failures and unresponsive service #42237).
- a file named
OpenCode version
OpenCode Desktop 2.0.16 (Windows installer), bundled/managed CLI 2.0.16,
channel=latest
Operating System
Windows 11 22H2 (10.0.22621) x64
Terminal
Desktop app (Electron) — not a terminal session
Related issues
- core: session never drains when Snapshot.capture() stalls on huge untracked tree — git add hangs, execution claim never released #48657 —
Snapshot.capture()stalls on huge untracked tree (root trigger; web/dev setup) - snapshot: corrupt index causes repeated capture failures and unresponsive service #42237 — corrupt index causes repeated capture failures and unresponsive service
- cli: service becomes unresponsive under multi-session load — single process bottleneck #49095 — service becomes unresponsive under multi-session load
- cli: service restart can silently reuse an unresponsive incumbent #37795 — service restart can silently reuse an unresponsive incumbent
Activity
4Liberty commentedon Sep 27, 2026
Independent confirmation from a heavily loaded Windows machine (32 logical cores, 61 GB RAM,
many open projects, several agent sessions in parallel):
The ~6-second window is the whole problem. Polling the service health endpoint from a second
process every 500 ms for 7 minutes measured: 19 samples over 1 s, 9 over 2 s, worst 4.45 s,
and four requests that did not answer within 4 s - all while the service was alive and its
own CPU stayed under one core. The 2 s liveness timeout sits inside the service's normal tail
latency under load, so any ~6-second burst becomes a kill + full respawn.
Snapshot capture is one trigger, not the only one: after disabling snapshots here, the same
loop persists with other long spans - POST /api/worktree/refresh and GET /api/vcs/diff running
30-62 s and then aborting (HTTP 499) when the app gives up, plus bursts of parallel tool-call
process spawns. Every replacement re-initializes everything: ~150 MCP server processes, ~32
other MCP instances, and tens of thousands of file-watcher subscriptions - a full respawn
storm exactly when the host is already saturated (measured 1-2 GB free RAM, 3.5-7.4k pages/s
paging), making the next burst more likely.
Suggestion along the lines already raised: derive liveness from something request work cannot
block (a dedicated endpoint/thread, or "process alive + descriptor valid"), and/or back off
replacement frequency instead of re-running full initialization on every strike.
vheins commentedon Sep 28, 2026
Independent confirmation from Linux (this report and the comment above are Windows; the other linked reports are macOS). Same mechanism, and it is not limited to snapshot capture.
Environment
~/.opencode/bin/opencode), Bun 1.4.0Observed loop (~10 h of log)
serve --servicestarts/api/info→ HTTP 503Background service is unresponsive; recovery cannot preserve persistent terminalsevent stream disconnecteduncaught exception: write EPIPEConsistent ~5 s pairing:
Confirms the ~6 s window
Inspecting the shipped v2.0.18 binary, the ensure path uses:
so the effective window is ~6 s (3 × 2 s), matching 4Liberty's measurement above. In my case
/api/infonormally answers in 36–413 ms, but during load bursts it exceeded that window while the process was alive and later recovered.Snapshots disabled, loop persisted
I set
"snapshots": false; the git capture spawns went to zero but the restart loop did not stop on its own — consistent with the comment above that snapshot capture is one trigger, not the only one.Same-second contention still present
Observed bursts with no effective serialization: 5 starts within one second (05:28:30), 4 at 04:05:22, 3 at 03:25:30.
Workaround that helped here
OPENCODE_DISABLE_FILEWATCHER=true. Watcher churn is a major load source on this host: 3,556watcher startedvs 1,467watcher stopped, and 8,027 subscribes to a symlinked skills directory.Happy to attach sanitized log excerpts.
piknockyou commentedon Sep 30, 2026
Your analysis of the snapshot step matches what I hit, so I'm adding the second trigger in case it helps others landing here with the same symptom (no reply and/or the
server restarted while you were workingloop,event stream disconnected / stalled).In my case the huge tree was not a real project: a stray empty
.gitin a parent folder (my home folder) made opencode treat everything underneath it as one giant project. Same result you describe - the per-message scan takes ~26s for me (541k files), the watchdog gives up, the service gets restarted, and it loops.For anyone with this symptom: run
git rev-parse --show-toplevelin the folder where you start opencode..gitis the culprit - delete it (usually empty, nothing lost; rename if unsure) and start opencode inside a real project folder. That was the fix for me.node_modules/,target/,build/, caches) into.gitignore, or set"snapshot": false.