Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server (the button feedback part is in apps/web)
Steps to reproduce
- Make a folder that is not a git repository and holds a lot of untracked data. About 8 GB across 34 text files of 240 MB each reproduces it on an SSD. Each file must stay under git's 512 MB
core.bigFileThreshold, otherwise git treats it as binary and skips counting its lines.mkdir demo && cd demo python3 -c 'l="x"*79+"\n"; open("seed.txt","w").write(l*3000000)' for i in $(seq 1 34); do cp seed.txt part$i.txt; done && rm seed.txt
- Add the folder as a project (Add project → Local folder) and start a new thread in it. The thread details panel shows Initialize Git.
- Click Initialize Git.
Expected behavior
git init takes tens of milliseconds, so the thread should switch to its repository state (branch name, Commit, Changes) right away. At minimum the button should show a busy state until that happens.
Actual behavior
The UI does not confirm the init for about 30 seconds. After that the branch name, Commit and Changes controls become usable.
- In the recording below, the button is replaced about a second after the click by dimmed, inert
main/Commit/Changes +0 −0rows, which stay that way for about 30 s. - In another run, the button stayed enabled and unchanged for more than 8 s after the click, with no pending or disabled state.
- In neither run is there a spinner, a toast or any other sign that something is happening.
Server trace for the recorded run (ws.rpc.vcs.init returns almost immediately, then the status refresh blocks):
16:35:24.575 ws.rpc.vcs.init 45 ms Success
16:35:24.623 VcsStatusBroadcaster.refreshStatus 31903 ms Success (ends 16:35:56.525)
16:35:26.451 GitVcsDriver.readBranchChangeTotals 30023 ms Failure (git diff --numstat hit the 30 s default timeout)
The first status update after init is built by readLocalStatus, which also computes the "Changes" row totals through readBranchChangeTotals in apps/server/src/vcs/GitVcsDriverCore.ts. For a repository with no commits, that runs git diff --numstat against the empty tree with every untracked file added to a temporary index. Git reads each file to count lines. REVIEW_UNTRACKED_MAX_FILES limits the file count but not the total size. The call has no timeout of its own, so it waits for the 30 s default; the failure is then swallowed (Effect.orElseSucceed(() => undefined)), and only after that does the isRepo: true status reach the client.
The same totals are recomputed on every status refresh. In the traces of my regular install (a large, mostly untracked folder), each 30 s status poll hit the same 30 s timeout.
Impact
Minor bug or occasional failure
Version or commit
main @ 3f94487 (dev server, vp run dev, isolated state)
Environment
macOS 26 (Darwin 25.6), T3 Code web client in the in-app Browser panel, git 2.53
Logs or stack traces
See the spans above.
Screenshots, recordings, or supporting files
Recording of the run above (about 40 s):
c9579205-50b9-4791-ac75-41a9affb3407-414c6e10-e57c-43bc-8a52-129668c23813-mp4.mp4
Workaround
None in the app. A short wait, or restarting the server, brings the repository state up.
Related
- [Bug]: vcs.refreshStatus takes ~25s on branches with a large diff (--minimal in change-totals diff) #21858 is the same
readBranchChangeTotalsstep slow on a large diff. It was fixed in fix(server): avoid expensive diff minimization in status refreshes #21872 by dropping--minimal, which does not help here: the time goes to reading untracked files, not to diff minimization. - Overlapping VCS status refreshes multiply Git and network work #7076 covers overlapping status refreshes multiplying git work.
I have a small fix ready (a short timeout on the totals diff, so the row falls back to the uncommitted totals, plus keeping the button busy until the status reports the new repository). I'll wait for triage before opening a PR.
Note
Responses generated with Claude (model: Claude Sonnet 5.5)
Activity
luis-7654 commentedon Nov 7, 2026
Additional reproduction for the same unbounded untracked-input path: a single sparse file can stall Changes totals, even in a tiny repository.
Observed in a Linux self-hosted T3 0.1.5 environment: an untracked crash dump had 1,378,833,166,336 bytes of logical size, but only 9,240,064 bytes allocated. The active
git diff --numstat -z <base> --process used T3's temporary review index, held that file open and consumed approximately one CPU core. The remaining tracked repository was about 5.45 MB. Removing the dump and excluding future dump files stopped the scan; a direct regular-index diff then completed in 0.0078 s. This last measurement is not a T3 UI benchmark or the exact temporary-index path.The same missing preflight remains in current main
4c3a9d58184f20bd76833bd599c3544d2c09e3eb:prepareUntrackedReviewIndexcaps file count but passes all candidate paths toprepareReviewIndex, which adds intent-to-add entries beforereadBranchChangeTotalsreads numstat. Output caps cannot prevent reading a huge input. The selected-file 1 MB expansion guard runs later and does not protect this path.A synthetic Git-level reproduction in a disposable container (no network, one CPU, 256 MiB RAM, read-only root, 16 MiB tmpfs) used one 8 GiB sparse file with only 4 KiB allocated. A fresh temporary index with
git add --intent-to-addfollowed bygit diff --numstatexceeded a two-second subprocess limit. A preflight prototype using logical file size and excluding the oversized untracked input before adding it to the index completed the diff in about 3.5 ms. This is a mechanism reproduction, not a patched T3 integration test.Suggested scope: bound per-file and aggregate untracked input bytes before content-reading Git commands, preserve normal file review, and explicitly report incomplete/skipped large-file statistics rather than presenting partial totals as exact. Reuse the policy for Changes totals and review previews. Include a focused regression using a sparse fixture so tests do not allocate huge files. Timeout remains a fallback; filename-only crash-dump exclusions would not cover other oversized files.
No crash dump contents, credentials or private workspace paths are included. The suspected browser crash that created the dump is a separate, unconfirmed cause and need not be part of this bug fix.
Implementation submitted in #32463: 10 MiB per untracked file, 64 MiB in total, with incomplete previews and omitted unavailable branch totals. Both focused regression tests fail on the base commit and pass with the patch.
vp run check:changed --concurrency 1passed all steps, including 242 tests across two files. Upstream CI and maintainer review remain separate.Prepared with GPT-6.1 Sol in the Codex harness.