ruvnet/ruflo · error · Error
repository changed while capturing exact source state
Error message
repository changed while capturing exact source state
What it means
On the clean-worktree fast path, the collector reads `git status --porcelain=v1 -z --untracked-files=all --ignore-submodules=none` and HEAD twice, around its tree-id reads. If the second status is non-empty or HEAD moved, the repository mutated during capture and the error aborts rather than binding a sourceStateId to a state that no longer exists.
Source
Thrown at v3/@claude-flow/codex/src/harness/repository-state.ts:426
const requested = repoPath instanceof URL ? fileURLToPath(repoPath) : repoPath;
const repoRoot = realpathSync(resolve(requested));
const top = realpathSync(gitText(repoRoot, ['rev-parse', '--show-toplevel']));
if (top !== repoRoot) throw new Error(`repoPath must be the Git top-level: ${top}`);
const cleanStatus = (): Buffer => gitBuffer(repoRoot, [
'status',
'--porcelain=v1',
'-z',
'--untracked-files=all',
'--ignore-submodules=none',
]);
const initialStatus = cleanStatus();
if (initialStatus.length === 0) {
repositoryPaths(repoRoot, false);
const baseCommit = gitText(repoRoot, ['rev-parse', '--verify', 'HEAD']);
const treeId = gitText(repoRoot, ['rev-parse', '--verify', 'HEAD^{tree}']);
if (cleanStatus().length !== 0 || gitText(repoRoot, ['rev-parse', '--verify', 'HEAD']) !== baseCommit) {
throw new Error('repository changed while capturing exact source state');
}
const repository = repositoryIdentity(repoRoot);
const identity = {
version: 1,
scope: 'git-visible',
repositoryId: repository.repositoryId,
commit: baseCommit,
tree: treeId,
};
return {
version: 1,
scope: 'git-visible',
kind: 'clean',
clean: true,
repository,
baseCommit,
treeId,
sourceStateId: digest(canonicalJson(identity)),View on GitHub (pinned to fa13ee4ad6)
Solutions
- Ensure no other git writers run against the worktree during capture and retry
- Disable IDE auto-fetch and background git hooks for the capture window
- Prefer capturing from a clean checkout dedicated to the capture
Defensive patterns
Strategy: retry
Validate before calling
const headA = gitText(root, ['rev-parse', 'HEAD']); const statusA = gitText(root, ['status', '--porcelain=v1', '-z']); const headB = gitText(root, ['rev-parse', 'HEAD']); const still = headA === headB && statusA === gitText(root, ['status', '--porcelain=v1', '-z']);
Try / catch
try { return capture(root); }
catch (e) {
if (/repository changed while capturing/.test(String(e)) && quiesced) return capture(root);
throw e;
} Prevention
- Run captures in a checkout dedicated to the snapshot
- Disable IDE auto-fetch and background git hooks during capture
- Serialize git writers with a lock if multiple tools share the clone
When it happens
Trigger: Anything touching the repo between the two reads: a concurrent `git fetch` updating refs, a hook or editor committing, an auto-formatter writing files, another agent session in the same worktree.
Common situations: Snapshotting while a CI checkout step, IDE auto-fetch, or another agent runs in the same clone; release tooling capturing evidence while a background job commits.
Related errors
- repository changed while hashing untracked entry: ${path}
- Git-visible source state does not match the build evidence r
- Git-visible source state changed before build evidence recom
- unsafe repository-relative path: ${path}
- repository path escapes root: ${candidate}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/a1dec23ef58900c0.
Report an issue: GitHub.