Triage: real server checkpoint bug. Confirmed on current main. Your 0.0.43-nightly.20260925.2269 is after #10792 and #12154, and this path is still there. Not a duplicate of #12080 / #12154 (sparse S flags and the listing cap), #3646 / #12924 (the old always-fresh index), or open #13543.
What you hit. For a non-sparse repo, capture copies the index, runs read-tree --reset HEAD, then streams git ls-files --full-name --sparse -v -z. Any lowercase tag, or S when core.sparseCheckout is not set, sets specialFlags in apps/server/src/vcs/GitVcsDriver.ts. Reuse is rejected, the private index is deleted, and capture seeds a stat-less index with read-tree HEAD and then git add -A -- .. That add uses the default 30s VcsProcess timeout. The timeout text is only VCS process timed out in <operation>: git (<cwd>) after <ms>, with no argv, so the failing command is not recorded. The seven successful Git calls you saw, then a ~30s failure, match this sequence: rev-parse --git-common-dir, rev-parse --verify HEAD, config --bool core.sparseCheckout, rev-parse --git-path index, read-tree --reset HEAD, ls-files -v -z, fresh read-tree HEAD, then add -A. SMB latency makes the full rehash miss 30s; the trigger is the fallback, not the mapped drive. Six flagged entries are enough.
Why the fallback exists. #10792 kept it on purpose instead of normalizing flags. git add -A will not record edits or deletions for assume-unchanged or skip-worktree paths, and the checkpoint has to. #12154 only stopped treating ordinary sparse-checkout S bits as manual flags. The comment in source is still "Manual flags still need a reset." Clearing the flag bits alone is not enough: the private index is backdated so Git will trust a same-size, same-mtime stat cache. That matches what you saw.
Direction. Selectively rebuilding only the flagged entries in the private index is the right fix. After the existing copy and read-tree --reset HEAD, feed those entries to update-index -z --index-info on GIT_INDEX_FILE so they keep their mode, OID, and stage, lose CE_VALID / CE_SKIP_WORKTREE, and get an empty stat cache. Leave every other entry's metadata in place. add -A then rehashes the flagged paths and still skips unchanged ones.
Constraints for a PR:
- Touch only the private index. The real index and Git config stay as they are.
- Put the conservative index timestamp back after
update-index, not only afterread-tree. Otherwise the racy-git protection from #10792 is lost. - On any failure, keep today's fresh-index fallback.
- Do the same for manual flags on a sparse checkout: lowercase tags, and in-cone paths that
sparse-checkout check-rulesselects. One manual flag there still forces a full rebuild. Do not clear ordinary out-of-coneSbits; #12154 depends on those staying so excluded paths are not recorded as deletions. - Invalid UTF-8 paths already force
specialFlags. Keep the fallback for those instead of round-tripping them through strings. - Cover this in
GitVcsDriver.test.ts: one skip-worktree entry and one assume-unchanged entry must not take the freshread-tree HEADpath; unflagged entries must keep their stat metadata; the snapshot must still include flagged edits and deletions, including same-size same-mtime edits; the real index must stay byte-identical. If a split index or submodule cannot be updated this way, fall back rather than guessing.
#13543 does not fix this. It raises git add -A to 5 minutes and reuses a saved private index on later whole-worktree, non-sparse captures. It does not run the flag check on that saved index because it assumes the index was already clean or rebuilt from read-tree HEAD. The first capture, any capture that times out before an index is saved, and every nested or sparse capture still take this fresh-index path. A cold add -A that exceeds the stage budget never writes the saved index, so every turn stays slow. The two changes compose only if flags are cleared before staging, so the saved index has none.
Workaround. None that preserves the flags. Unsetting the six skip-worktree bits would allow reuse and would also change the real index. Do not treat "update the nightly", "wait for #13543", or "raise the timeout" as closing this. A separate change to print the Git subcommand on timeout is unnecessary to confirm this failure.