Repository navigation
VC-834: background Change Set/rail/venue/diff reads run read-only git (stacked on #885 — merge after it) - #909
Merged
hussainph merged 3 commits intoOct 19, 2026
Conversation
…cks) The Change Set snapshot and path set, the rail status, the venue snapshot and diffStat ran a plain git status, which holds the checkout's index.lock for its whole untracked scan. These run in the background within a second of any write, so an agent's own git add/commit (or a pre-commit hook's git add) in the same checkout could fail with 'index.lock: File exists'. Each entry point now wraps its runner in readOnlyGitAsync, the orphan scan's existing wrapper. Mutations (commit, sync, worktree add/remove) are unchanged.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Resolve agent-commands.test.ts: keep main's dropped sync seam
({ gitAsync } only) and this branch's scriptedReadOnlyGit. Move the two
new VC-802 conflicts fixtures that drive changeSetPaths onto
scriptedReadOnlyGit, since their handlers dispatch on the verb.
…nd-change-set-rail-venue-git-status-read
This was referenced Oct 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Defect
git statusholds the checkout'sindex.lockfor its whole run, untracked scan included, so it can write back the stat data it refreshed (git-status(1), "BACKGROUND REFRESH"). The orphan scan already avoids this withreadOnlyGitAsync(--no-optional-locks). The reads that run most often did not:changeSetSnapshot/changeSetPaths: refetched within ~1 s of any write in the worktreegetWorktreeStatus: the rail, on every mount and watch eventvenueSnapshot: also runs on the Main checkout that Board Sessions sharediffStatSo an agent's own
git add/commit, or a pre-commit hook'sgit addafter a formatter rewrote files, could race Volli's backgroundstatus -ualland fail withindex.lock: File exists.Verified locally with real git 2.48.1 (not only by reading code). I paused a plain
git status --porcelain=v2 -z -uallmid-way through its untracked scan. While it was paused,.git/index.lockexisted and a concurrentgit add -ufailed withUnable to create '…/index.lock': File exists. With--no-optional-locksthere was no lock and the samegit addsucceeded.Fix
Each of the five entry points wraps its runner once in the existing
readOnlyGitAsync(scan.ts). Every git call they make, including the helpers they passgitto (comparison-ref resolution, the sequencer probe), leads with--no-optional-locks. User-initiated mutations (commit.ts,sync.ts,worktree add/remove) anddirty.tsare untouched: the confirmed-delete probe is out of scope per the ticket, and the scan's probe was already read-only. No retries were added.Test-only:
scriptedGitgains an opt-inscriptedReadOnlyGitvariant that strips the leading switch before the handler and the recordedcallssee the args. That lets the existing suites keep dispatching on the verb and asserting exact argv unchanged.How the regression test proves it
packages/host-core/src/worktree/read-only-reads.test.ts:scan.test.ts:171shape): forchangeSetSnapshot,changeSetPaths,getWorktreeStatus,venueSnapshotanddiffStat(both modes), every recorded arg list starts with--no-optional-locks.statusthey issue, a tracked file is made stat-dirty (same bytes, new mtime). That is exactly what a plain status refreshes underindex.lockand writes back. The test asserts all fivestatusinvocations leave.git/indexbyte-identical. A control test shows a plainstatusdoes rewrite it, so the fixture can see a lock being taken.On the base (fix stashed), 7 of 8 fail (the control passes); with the fix, 8/8 pass.
The ticket asked for a concurrent-
git addrace test. I swapped it for this deterministic write check because a timing race would be flaky in CI. The concurrency itself was shown by hand, as described above.For the maintainer to decide
git diffstill takesindex.lockbriefly.builtin/diff.c'srefresh_index_quietly()locks and writes the index at the end of a working-tree diff whenever it found stat-dirty entries. It is gated bydiff.autoRefreshIndex, not by--no-optional-locks: I checked this in git master and saw it on 2.48.1. That window is much shorter thanstatus's (refresh + write, not the untracked walk), but it is the same symptom. Closing it needs eitherdiff.autoRefreshIndex=falseon the read runner (localGitArgsadmits no-ctoday) or a move to plumbingdiff-index. Both are outside this ticket's scope, so they are not done here. The real-git test measures onlystatusand says why.trim.tstrackedChangeRefusal(status --porcelaininside a trim) is in the ticket's evidence list but not in its proposed fix, so it is unchanged.status --porcelainread inreconcile.ts(uncommittedChanges) that should take the read-only form too. It is not on this branch's base.Checks run locally
vp test runon: read-only-reads, change-set, status, venue, diff, read, ensure, snapshot, git-regressions, git-parity, scan, sync, publish, watch, worktree-runtime, handlers/host-handlers, agent-commands, agent-dispatch. All green.vp check: pass.vp run --filter @volli/host-core typecheck: pass.