ruvnet/ruflo · error
Git-visible source state does not match the build evidence r
Error message
Git-visible source state does not match the build evidence request
What it means
captureBuildEvidence recomputes the repository's ExactSourceState via captureRepositorySourceState(repoRoot) and compares its sourceStateId with the one you passed in. The id binds the git-visible source state (commit plus tracked and untracked content — HEAD alone is not identity in a dirty worktree), so any commit, checkout, branch move, or file change between your capture and this call yields a different id and throws.
Source
Thrown at v3/@claude-flow/codex/src/harness/build-evidence.ts:153
buildInputs: inputs,
toolchains: tools,
};
return { ...body, evidenceDigest: sha256(canonicalJson(body)) };
}
/**
* Recompute declared evidence from local bytes. It does not prove the
* declaration set is complete and does not sign or authorize a release.
*/
export function captureBuildEvidence(
repoPath: string,
sourceState: ExactSourceState,
buildInputs: readonly BuildInputDeclaration[],
toolchains: readonly ToolchainDeclaration[],
): BuildEvidence {
const repoRoot = realpathSync(resolve(repoPath));
if (captureRepositorySourceState(repoRoot).sourceStateId !== sourceState.sourceStateId) {
throw new Error('Git-visible source state does not match the build evidence request');
}
const inputs = buildInputs.map((declaration): DeclaredBuildInput => {
const path = normalizePath(declaration.path);
const absolute = resolve(repoRoot, path);
const real = realpathSync(absolute);
const rel = relative(repoRoot, real);
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
throw new Error(`build input escapes repository: ${path}`);
}
return { name: declaration.name, path, ...digestPath(real, true) };
});
const tools = toolchains.map((declaration): DeclaredToolchain => ({
name: declaration.name,
version: declaration.version,
digest: digestPath(
isAbsolute(declaration.path) ? declaration.path : resolve(repoRoot, declaration.path),
true,
).digest,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Capture the ExactSourceState immediately before calling captureBuildEvidence and pass that fresh object, with nothing running that can mutate the repo in between
- Commit or stash all changes (including untracked) before capture so the state is stable
- If mutation is unavoidable, snapshot the repo (copy or archive) and run capture and evidence creation against the snapshot
Example fix
// before const state = captureRepositorySourceState(repoRoot); await runInstallStep(); // mutates lockfile/dirty state const evidence = captureBuildEvidence(repoPath, state, inputs, tools); // throws // after await runInstallStep(); const state = captureRepositorySourceState(repoRoot); // capture last const evidence = captureBuildEvidence(repoPath, state, inputs, tools);
Defensive patterns
Strategy: validation
Validate before calling
import { captureRepositorySourceState } from './harness/repository-state.js';
const fresh = captureRepositorySourceState(repoRoot);
if (fresh.sourceStateId !== sourceState.sourceStateId) {
throw new Error('source state drifted since capture; re-capture before building evidence');
}
const evidence = captureBuildEvidence(repoPath, fresh, inputs, tools); Try / catch
Catch around captureBuildEvidence; on the mismatch message, re-capture the source state and either retry once with the fresh state (if the mutation was expected) or abort (if it signals unexpected drift).
Prevention
- Capture source state as the last step before evidence creation — no installs, formats, or commits in between
- Run evidence capture against a snapshot or clean checkout in CI
- Disable watchers/formatters for the duration of capture in local runs
When it happens
Trigger: Capturing sourceState, then committing, editing, or deleting files (including untracked ones) before calling captureBuildEvidence; a formatter, watcher, or install step mutating the tree in between; capturing against a different checkout or submodule state than the one passed as repoPath.
Common situations: CI pipelines where a prepare step touches files between state capture and evidence capture; local runs with auto-format-on-save or file watchers active; parallel agents committing into the same repository mid-flight.
Related errors
- Git-visible source state changed before build evidence recom
- ${label} must be non-empty
- ${label} must be a canonical sha256 digest
- unsafe build input path: ${value}
- build evidence path is not a file or symlink: ${path}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/1642ee669c88f3dd.
Report an issue: GitHub.