What happened
Git does not lock .git/worktrees/ against a second worktree command. Two that overlap can fail, because one reads the other's half-written record or finds the directory gone.
#548 fixed this inside the orchestrator: every worktree command there goes through one queue per repository. Two callers remain outside that queue.
LocalGitSource.writeState in packages/core/src/sources/local-source.ts runs git worktree list on every state write. When a fold removes the last worktree, git deletes .git/worktrees/ and a list running at that moment can fail.
- The server and the CLI are other processes. A decision recorded in Gatehouse while the engine is cutting or removing a worktree can overlap with it, and no queue inside one process can prevent that.
What you expected
A state write does not fail because a worktree command was running.
Reproduction
Not reproduced for these two callers. In a scratch repository, two concurrent sequences of worktree list, prune and add failed 2 times in 600.
Work
Found while fixing #547. Part of #492.
What happened
Git does not lock
.git/worktrees/against a second worktree command. Two that overlap can fail, because one reads the other's half-written record or finds the directory gone.#548 fixed this inside the orchestrator: every worktree command there goes through one queue per repository. Two callers remain outside that queue.
LocalGitSource.writeStateinpackages/core/src/sources/local-source.tsrunsgit worktree liston every state write. When a fold removes the last worktree, git deletes.git/worktrees/and a list running at that moment can fail.What you expected
A state write does not fail because a worktree command was running.
Reproduction
Not reproduced for these two callers. In a scratch repository, two concurrent sequences of worktree list, prune and add failed 2 times in 600.
Work
writeStateneedsgit worktree liston every write, and whether the answer can be remembered between writes.Found while fixing #547. Part of #492.