Repository navigation
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.opencodejr commentedon Oct 28, 2026
Thanks for the detailed report and the measurements.
I reproduced this on 2.0.16 (on Linux, with the managed background service). In a git repo with one empty commit and 200k untracked, non-ignored files under
node_modules/, sending a prompt kept the service busy and/api/infotimed out at 2s for more than 28s. The next client call then loggedBackground service is unresponsive; recovery cannot preserve persistent terminalsand killed the service, which matches what you saw.I ran the same steps on the latest
dev(9adc42b). The service still ran the snapshot capture over the whole tree, but/api/infoanswered in a few milliseconds the whole time and the service wasn't replaced. So it looks like this is already fixed, and the fix should be in an upcoming release.There are two caveats:
Slow capture on very large untracked trees is tracked in #48657. The
nulfile and staleindex.lockproblems you mentioned are separate; #42237 covered the snapshot index failures.The bot will close this issue in 1 day unless you reply that it still happens, for example on a newer version than 2.0.16.
opencodejr commentedon Oct 29, 2026
Closing this as completed, since there hasn't been a reply in the day since my last comment.
I could make the service get killed on 2.0.16: snapshot capture over a large untracked tree blocked
/api/info, and the client then replaced the busy service. On the latestdev(9adc42b), the same steps didn't cause it. The server kept answering while the capture ran, so the fix should be in an upcoming release.If you still see the restart loop on a newer version, please comment here or open a new issue with your version and the Desktop and CLI logs. Related: #48657 (slow capture on huge untracked trees), and #58130 and #52049 (the client replacing a service that is busy but still running). Thanks again for the detailed report.