{"record":{"id":"17b1b48664441df8","repo":"ruvnet/ruflo","slug":"git-visible-source-state-changed-before-build-evid","errorCode":null,"errorMessage":"Git-visible source state changed before build evidence recomputation","messagePattern":"Git-visible source state changed before build evidence recomputation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/build-evidence.ts","lineNumber":183,"sourceCode":"  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,\n  }));\n  return createBuildEvidence(sourceState, inputs, tools);\n}\n\nexport function recomputeBuildEvidence(\n  repoPath: string,\n  expected: BuildEvidence,\n  toolchains: readonly ToolchainDeclaration[],\n): BuildEvidence {\n  const sourceState = captureRepositorySourceState(repoPath);\n  if (sourceState.sourceStateId !== expected.sourceStateId) {\n    throw new Error('Git-visible source state changed before build evidence recomputation');\n  }\n  return captureBuildEvidence(\n    repoPath,\n    sourceState,\n    expected.buildInputs.map(({ name, path }) => ({ name, path })),\n    toolchains,\n  );\n}\n\nexport function buildEvidenceMatches(expected: BuildEvidence, actual: BuildEvidence): boolean {\n  return expected.evidenceDigest === actual.evidenceDigest\n    && expected.sourceStateId === actual.sourceStateId;\n}\n","sourceCodeStart":165,"sourceCodeEnd":197,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/build-evidence.ts#L165-L197","documentation":"recomputeBuildEvidence first re-captures the repository source state and requires it to equal expected.sourceStateId — the id recorded when the evidence was created. If the repo has changed in any git-visible way since (commits, edited tracked files, changed untracked content), recomputation refuses to proceed because the stored evidence no longer describes these bytes.","triggerScenarios":"Calling recomputeBuildEvidence after new commits or file edits relative to when expected was produced; verifying evidence created on a different machine or checkout whose dirty state differs; a verification step that runs after tooling mutated the tree (npm install updating a lockfile, a formatter).","commonSituations":"CI pipelines where a prepare step touches files between build and verification; local verification runs after a formatter or npm install mutated the tree; verifying evidence produced on a different machine or branch.","solutions":["Reproduce the exact source state first: check out the bound commit and restore tracked/untracked bytes the evidence recorded (git stash/clean/checkout as needed)","If the current state is what you want to attest, re-create evidence from scratch with captureBuildEvidence instead of recomputing stale evidence","Run recomputation against an isolated snapshot of the repo so nothing can mutate it in between"],"exampleFix":"// before\nconst evidence = recomputeBuildEvidence(repoPath, evidenceFromYesterday, tools); // repo moved on\n\n// after\nconst fresh = captureRepositorySourceState(repoRoot);\nconst evidence = captureBuildEvidence(\n  repoPath,\n  fresh,\n  evidenceFromYesterday.buildInputs.map(({ name, path }) => ({ name, path })),\n  tools,\n);","handlingStrategy":"retry","validationCode":"import { captureRepositorySourceState } from './harness/repository-state.js';\nconst current = captureRepositorySourceState(repoRoot);\nif (current.sourceStateId !== expected.sourceStateId) {\n  // decide before calling: restore the expected state, or rebuild evidence from current\n}","typeGuard":null,"tryCatchPattern":"try { return recomputeBuildEvidence(repoPath, expected, tools); } catch (error) { if (error instanceof Error && error.message.includes('Git-visible source state changed')) { const fresh = captureRepositorySourceState(repoPath); return captureBuildEvidence(repoPath, fresh, expected.buildInputs.map(({ name, path }) => ({ name, path })), tools); } throw error; }","preventionTips":["Pin the verification environment: clean checkout at the evidence's commit before recomputing","Keep build and verification steps in one job with no tooling in between that touches files","Treat a mismatch as drift evidence worth reporting, not noise to suppress"],"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-14T00:17:10.932Z"}