{"record":{"id":"1642ee669c88f3dd","repo":"ruvnet/ruflo","slug":"git-visible-source-state-does-not-match-the-build","errorCode":null,"errorMessage":"Git-visible source state does not match the build evidence request","messagePattern":"Git-visible source state does not match the build evidence request","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/build-evidence.ts","lineNumber":153,"sourceCode":"    buildInputs: inputs,\n    toolchains: tools,\n  };\n  return { ...body, evidenceDigest: sha256(canonicalJson(body)) };\n}\n\n/**\n * Recompute declared evidence from local bytes. It does not prove the\n * declaration set is complete and does not sign or authorize a release.\n */\nexport function captureBuildEvidence(\n  repoPath: string,\n  sourceState: ExactSourceState,\n  buildInputs: readonly BuildInputDeclaration[],\n  toolchains: readonly ToolchainDeclaration[],\n): BuildEvidence {\n  const repoRoot = realpathSync(resolve(repoPath));\n  if (captureRepositorySourceState(repoRoot).sourceStateId !== sourceState.sourceStateId) {\n    throw new Error('Git-visible source state does not match the build evidence request');\n  }\n  const inputs = buildInputs.map((declaration): DeclaredBuildInput => {\n    const path = normalizePath(declaration.path);\n    const absolute = resolve(repoRoot, path);\n    const real = realpathSync(absolute);\n    const rel = relative(repoRoot, real);\n    if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {\n      throw new Error(`build input escapes repository: ${path}`);\n    }\n    return { name: declaration.name, path, ...digestPath(real, true) };\n  });\n  const tools = toolchains.map((declaration): DeclaredToolchain => ({\n    name: declaration.name,\n    version: declaration.version,\n    digest: digestPath(\n      isAbsolute(declaration.path) ? declaration.path : resolve(repoRoot, declaration.path),\n      true,\n    ).digest,","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/build-evidence.ts#L135-L171","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst state = captureRepositorySourceState(repoRoot);\nawait runInstallStep(); // mutates lockfile/dirty state\nconst evidence = captureBuildEvidence(repoPath, state, inputs, tools); // throws\n\n// after\nawait runInstallStep();\nconst state = captureRepositorySourceState(repoRoot); // capture last\nconst evidence = captureBuildEvidence(repoPath, state, inputs, tools);","handlingStrategy":"validation","validationCode":"import { captureRepositorySourceState } from './harness/repository-state.js';\nconst fresh = captureRepositorySourceState(repoRoot);\nif (fresh.sourceStateId !== sourceState.sourceStateId) {\n  throw new Error('source state drifted since capture; re-capture before building evidence');\n}\nconst evidence = captureBuildEvidence(repoPath, fresh, inputs, tools);","typeGuard":null,"tryCatchPattern":"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).","preventionTips":["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"],"tags":["build-evidence","source-state","git","reproducibility","harness"],"backgroundTag":"source-state-mismatch","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}