Fixes #3 - #5
Fixes #3#5
Conversation
…ingCommit in worktree provisioning Concurrent worktree-provisioning dispatches for the same repo could race: a `git fetch` writing refs/remotes/* on one thread could interleave with `git rev-parse --verify refs/remotes/...` on another, failing with "fatal: Needed a single revision". Serialize fetch, resolveRemoteTrackingCommit, and worktree add per repo (keyed by gitCommonDir) so the same clone's git-prep never runs concurrently, while different repos keep provisioning in parallel. Fixes #3 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
VERDICT:APPROVED SummaryThe fix correctly resolves the race described in #3: a per-repo semaphore (keyed by Correctness — verified
Non-blocking notes
None of these block. The change is minimal, correct, well-commented, and directly targets the incident. Approving. |
|
CI_FAILED These checks never started: Check, Mobile Native Static Analysis, Test, Release Smoke. Nothing picked them up, which points at a runner being down rather than at your change. Re-trigger them and confirm they go green. The review approved this PR, so CI is the only thing standing between it and merge. |
…po max=1 Review on #5 flagged that the lock test only asserted maxActiveByRepo===1 per repo, which would also pass under a regressed single global lock - the "but not across repos" half of the test name was unverified. Track global concurrent guarded-subprocess count and assert it reaches 2, so a regression to one shared semaphore across repos fails the test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressed the CI_FAILED notice. Root cause of the stuck checks: run 33891614463 sat in Used the opportunity productively: rather than push an empty commit, I addressed the reviewer's non-blocking note #1 from the APPROVED review — the lock test asserted Notes non-blocking items #2 and #3 from the review (other ref-writing fetch paths left unguarded; Verification: could not run A fresh CI run is now in flight: https://github.com/fzoll/t3code/actions/runs/33897675989. Please confirm it goes green — I can't block this turn on its completion. |
|
VERDICT:APPROVED I independently reviewed the diff against issue #3 (verifying claims directly on What the fix doesA per-repo single-permit Correctness — verified on the branch
Test qualityThe added Non-blocking notes (residual, documented as out of scope)
None of these block. The change is minimal, correct, well-commented, and directly resolves the race in #3. Approving on the merits of the code. (CI status is outside this review's scope; the workflow-run/runner issues discussed in earlier comments are unrelated to the correctness of the change.) |
|
CI_FAILED These checks never started: Check, Test, Release Smoke, Mobile Native Static Analysis. Nothing picked them up, which points at a runner being down rather than at your change. Re-trigger them and confirm they go green. The review approved this PR, so CI is the only thing standing between it and merge. |
|
Re: CI_FAILED (2026-09-04T17:55:21Z) — investigated instead of pushing another empty/no-op commit, because the prior round already tried exactly that (commit 7cd1912, pushed specifically to get a fresh CI dispatch after run 33891614463 sat queued for >1h) and it reproduced the identical symptom. Evidence this is a runner-pool outage, not a repo/code issue: Every
All four jobs ( Switching No further code changes to make in this round — the fix itself is approved twice (VERDICT:APPROVED at 15:52 and 16:57) and this queued-forever state reproduces identically across unrelated branches/commits, which rules out "re-trigger with a new commit" as a fix. This needs the Blacksmith runner pool restored at the account level. |
|
VERDICT:APPROVED I reviewed the diff independently against issue #3, verifying each claim directly in the source on What it doesA per-repo single-permit Correctness — verified in source
Test qualityThe Non-blocking notes (residual, documented as out of scope)
Verification caveatI confirmed imports ( None of the notes block. The change is minimal, correct, well-commented, and directly resolves the race in #3. Approving on the merits of the code; CI/runner status is outside this review's scope. |
* fix(server): per-repo semaphore around git fetch + resolveRemoteTrackingCommit in worktree provisioning Concurrent worktree-provisioning dispatches for the same repo could race: a `git fetch` writing refs/remotes/* on one thread could interleave with `git rev-parse --verify refs/remotes/...` on another, failing with "fatal: Needed a single revision". Serialize fetch, resolveRemoteTrackingCommit, and worktree add per repo (keyed by gitCommonDir) so the same clone's git-prep never runs concurrently, while different repos keep provisioning in parallel. Fixes #3 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(server): assert cross-repo git-lock concurrency, not just per-repo max=1 Review on #5 flagged that the lock test only asserted maxActiveByRepo===1 per repo, which would also pass under a regressed single global lock - the "but not across repos" half of the test name was unverified. Track global concurrent guarded-subprocess count and assert it reaches 2, so a regression to one shared semaphore across repos fails the test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Per-repo (gitCommonDir-keyed) semaphore around the worktree-provisioning git sequence (
fetch+resolveRemoteTrackingCommit+worktree add) so the same clone's git-prep never runs concurrently, while different repos keep provisioning in parallel.Root cause
resolveRemoteTrackingCommitrunsgit rev-parse --verify refs/remotes/<remote>/<branch>^{commit}in the main clone. If a concurrentgit fetch(from a second, simultaneous worktree-provisioning dispatch against the same repo) is writing/lockingrefs/remotes/*at that moment, therev-parsefails withfatal: Needed a single revision. There was no per-repo lock around this sequence — only settings/sqlite/provider access was serialized, not the git operations themselves.Fix
In
apps/server/src/vcs/GitVcsDriverCore.ts, added aRef<Map<gitCommonDir, Semaphore>>and awithRepoGitLock(cwd, effect)helper that resolves the repo'sgitCommonDirand runseffectunder that repo's dedicated single-permit semaphore. Wrapped the three operations implicated by the incident with it:fetchRemoteresolveRemoteTrackingCommitcreateWorktree(git worktree add)Every caller of these (the
ws.tsbootstrapprepareWorktreeflow that triggered the incident,GitManager.resolveBaseRangeRef,preparePullRequestThread, etc.) is protected automatically since the lock lives at the driver level, keyed by the resolvedgitCommonDirrather than the caller'scwd— so a worktree under the same repo also serializes against the main clone.Why this design
gitCommonDir(notcwd) so worktrees of the same repo share one lock, matching "same repo" from the issue rather than "same directory".Ref.modify), so there's no upfront enumeration of repos and no cross-repo blocking.Testing
apps/server/src/vcs/GitVcsDriverCore.test.ts: "serializes fetch + resolveRemoteTrackingCommit + worktree add for the same repo, but not across repos" — wraps the real spawner to track concurrently in-flight guarded git subprocesses per repo (holding each slightly past its real completion to widen any race window) and asserts max concurrency is 1 within a repo while two different repos still provision independently.vp test run src/vcs/GitVcsDriverCore.test.ts src/git/GitWorkflowService.test.ts src/git/GitManager.test.ts src/vcs/GitVcsDriver.test.ts— 100 tests passed.tsgo --noEmit(apps/server) — clean.vp checkon both changed files — formatting and lint clean.Per repo convention (AGENTS.md), I did not run the full-workspace
vp run typecheck/vp run test— verification was scoped to the changed files and their closest test suites.Scope note
This addresses the exact race described in the issue (concurrent worktree-provisioning dispatches against the same repo). It does not attempt the separate, explicitly-out-of-scope "bounded retry + offer-throttle" work referenced in the issue's footer, which belongs to
fzoll/cc_runner.