Repository navigation
Closed
Labels
Description
Summary
The desktop app on Windows becomes completely unresponsive for minutes at a time. Logs show two compounding cases:
- VCS diff spawns 2
git.exeprocesses per changed/untracked file, sequentially (~25-30 ms per spawn on Windows). With ~1,476 dirty files a singleGET /api/vcs/difftakes ~80 s, exceeding the client's 60 s timeout; the client aborts (HTTP 499) and retries immediately, pinning the server and freezing the UI. - During one episode the renderer issued 21,696
/api/fs/listrequests within ~4 minutes (~180 req/s peak), each recompute re-running the same git pipeline (5,245 git spawns in the same window) - even though the worktree only had ~185 dirty files at that point.
Environment
- Desktop app 2.0.32 (bundled CLI 2.0.32);
opencode --versionon PATH is 1.18.29 (separate older CLI install, not used by the desktop app) - OS: Windows 10 Pro 22H2, build 19045.6466 (win32 x64)
- Terminal: N/A (desktop app); server-side shell commands run via PowerShell 5.1 / cmd
- Install/channel: packaged release, auto-update channel
latest, updater reports 2.0.32 up to date - Active plugins: none (global config has no
pluginsentry; one MCP servervscodeis configured)
Reproduction
Intermittent, but observed repeatedly in one day of normal use:
- Desktop app on Windows, project worktree inside a git repo with several hundred changed/untracked files (first observed with ~1,476 files; later with only ~185).
- Have a session open in that project; switch sessions / let the session create or edit files.
- The UI stops responding to clicks for minutes (sometimes longer).
Server log (~/.local/share/opencode/log/opencode.log) shows:
- thousands of spawns, one pair (
--numstat+--patch) per untracked file:
spawning process command="C:\Program Files\Git\cmd\git.EXE" args="[…,"diff","--no-index","--patch",…,"--","/dev/null","<file>"]" - aborted diff requests:
Sent HTTP response http.span=60175 … GET http.url=/api/vcs/diff http.status=499
Desktop renderer log shows:
[server-request-queue] server thrashing detected(repeated every ~10 s during an episode)[global-sdk] event stream disconnected
Expected Behavior
- Diff/status is computed with a bounded number of git invocations (batch
git status/git difffor the whole tree), capped and cancellable when the client aborts. - No runaway
/api/fs/listpolling from the renderer; a timed-out request is not retried in a tight loop.
Actual Behavior
- The server spends minutes spawning ~30-50 git processes per second; every other request queues behind it.
- The desktop UI freezes completely (clicks do nothing) until the queue drains.
GET /api/vcs/diffaborts at the 60 s client timeout and immediately retries, sustaining the saturation./api/fs/listwas requested ~180x/s at peak for ~4 minutes with no corresponding user action.
Additional Context
Measured from opencode.log on 2026-10-18 (UTC):
| Hour | git.exe spawns | of which PNG asset diffs | note |
|---|---|---|---|
| 02:00 | 30,363 | 26,976 | /api/vcs/diff aborting after ~60 s (499), retrying |
| 03:00 | 20,698 | 14,594 | until untracked file count was reduced |
| 04:00 | 2,675 | 0 | |
| 11:00 | 11,263 | 0 | 5,245 of them inside the 11:33-11:39Z burst |
| 12:00 | 828 | 0 |
- Aborted
/api/vcs/diffspans: 60108 / 60101 / 60175 / 51125 ms (status=499). /api/fs/list: 21,696 responses between 11:34:09Z and 11:38:07Z (single peak minute: 10,848 ~= 181/s), then stopped on its own; cause unknown.- Thrashing reports (local time): 10:50:22-10:51:12, 11:21:56-11:22:46, 12:56:52, 12:58:29, 19:34:13-19:38:00.
- Per-spawn cost ~= 25-30 ms on this machine (derived from consecutive
spawning processtimestamps). - Reducing the dirty set (adding a 1,004-file unreferenced asset dir to
.gitignore) cut a single diff pass from >60 s (endless 499 retry loop) to ~23 s - still slow - and the UI froze again later during the/api/fs/listburst with only ~185 dirty files.
Activity
opencode-agent commentedon Oct 18, 2026
Thanks for the detailed numbers. I reproduced the per-file git spawning in
/api/vcs/diffon 2.0.32, and it still happens on the latestdev(da9d436c).Since 2.0.32, untracked files are diffed in one batch: a few git processes in total. That batch depends on
git add --intent-to-add --sparse. If that command fails, all untracked files go back to the old path of twogit diff --no-index … -- /dev/null <file>processes per file, one after another. Nothing limits how many files go down that path. That matches the spawn lines in your log.What I ran:
.pngfiles and a Git that rejects--sparse(as Git older than 2.45 does): 3,007 git spawns for oneGET /api/vcs/diff, about 8 s on Linux. At your measured 25-30 ms per spawn on Windows, that's roughly 75-90 s, past the 60 s client timeout.The script below reproduces it. I couldn't test the Windows desktop app here, so the
/api/fs/listrequest storm and the UI freeze itself are not confirmed yet.Could you share the output of
git --versionforC:\Program Files\Git\cmd\git.exe? If it is older than 2.45, that is very likely the trigger, and updating Git for Windows should work around it for now. If it is newer, something else is making the batch fail on Windows, and that would be useful to know.Related: #52953 (the same
--sparseproblem in snapshots) and the open PR #56527 (retry without--sparse). #55191 described a similar renderer request fan-out.Reproduction script (repro.sh)
Run it:
bash repro.shZhouRenX commentedon Oct 18, 2026
Here's the requested info, plus a before/after re-measurement after updating Git for Windows.
git --versionforC:\Program Files\Git\cmd\git.exe(the exact binary in the spawn lines; also the only git on PATH):Control test in a throwaway repo with the same command OpenCode runs:
git add --intent-to-add --force --sparse --ignore-errors --pathspec-from-file=<list> --pathspec-file-nul→ exit 129,
error: unknown option 'sparse'GET /api/vcs/diff(mode=working), same machine/repo:--no-indexThe 7-spawn case matches your "current Git: 7 spawns, 0.18 s":
rev-parse --verify HEAD,diff --name-status HEAD,diff --numstat HEAD,status --porcelain=v1 -uall,add --intent-to-add --force --sparse --pathspec-from-file=… --pathspec-file-nul,diff --numstat,diff --patch- no per-file--no-indexat all. No app restart was needed.So: trigger confirmed on Windows, and updating Git for Windows works around it. This machine had 2.31.1 (2021); Windows users with installs that old will still hit it, so #56527 (retry without
--sparse) is still valuable.The
/api/fs/listfan-out remains separate: 21,696 responses in 11:34:09Z-11:38:07Z (peak minute 10,848 ~= 181/s) withserver thrashing detectedat 19:34:13 / 19:34:33 / 19:36:47 / 19:38:00 local, and 5,245 git spawns in the same window. Happy to collect more instrumentation if useful.reynold-hu commentedon Oct 18, 2026
Some evidence from a Windows 11 machine (win32 x64) against current
dev:--numstat+--patchpair is the fallback path. The batched path (untrackedBatch, added in fix(core): batch untracked file diffs instead of spawning git per file #53449 on 2026-10-08) uses a throwaway index and three Git processes (add --intent-to-add,diff --numstat,diff --patch) for all eligible untracked files. 2.0.32 was published 2026-10-16, so the package should contain it.core.autocrlf=trueand CRLF content. The batch stayed engaged (spawn count far below the file count) and produced correct stats and patches.untrackedBatchreturned empty for that request (both the stats and patch maps), not just a per-file patch fallback. That points at the temporary-index setup (git add --intent-to-add ... --pathspec-from-file=...) failing silently on that repository, or at every file being filtered out before the batch.Could you check:
opencode-untracked-*temp files appear in%TEMP%during a diff, and does the log contain thegit add --intent-to-addinvocation withGIT_INDEX_FILE?git config --list --show-origin | findstr /i core.(especiallycore.splitIndex,core.autocrlf,core.ignorecase,core.precomposeunicode), and whether any files are symlinks or inside a nested repository?Separately, the
/api/fs/liststorm is client-side: the server only computes what is asked, and the desktop request queue logsserver thrashing detectedbut does not retry. 21,696 requests in four minutes means something in the renderer re-lists the tree per event; that seems worth a separate issue even after the Git work is bounded.