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

  1. Capture the ExactSourceState immediately before calling captureBuildEvidence and pass that fresh object, with nothing running that can mutate the repo in between
  2. Commit or stash all changes (including untracked) before capture so the state is stable
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/1642ee669c88f3dd. Report an issue: GitHub.