{"record":{"id":"d22d8691988bf6d7","repo":"ruvnet/ruflo","slug":"duplicate-declared-toolchain","errorCode":null,"errorMessage":"duplicate declared toolchain","messagePattern":"duplicate declared toolchain","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/build-evidence.ts","lineNumber":129,"sourceCode":"    bytes: input.bytes,\n  })).sort((left, right) => compare(left.path, right.path) || compare(left.name, right.name));\n  const tools = toolchains.map((toolchain) => ({\n    name: requireText(toolchain.name, 'toolchain name'),\n    version: requireText(toolchain.version, 'toolchain version'),\n    digest: requireDigest(toolchain.digest, 'toolchain digest'),\n  })).sort((left, right) => compare(left.name, right.name) || compare(left.version, right.version));\n\n  if (inputs.some(({ bytes }) => !Number.isSafeInteger(bytes) || bytes < 0)) {\n    throw new Error('build input bytes must be a non-negative safe integer');\n  }\n  const inputKeys = inputs.map(({ name, path }) => `${name}\\0${path}`);\n  if (new Set(inputKeys).size !== inputKeys.length) throw new Error('duplicate declared build input');\n  const foldedPaths = inputs.map(({ path }) => portableCaseFold(path));\n  if (new Set(foldedPaths).size !== foldedPaths.length) {\n    throw new Error('case-fold collision in declared build inputs');\n  }\n  const toolKeys = tools.map(({ name, version }) => `${name}\\0${version}`);\n  if (new Set(toolKeys).size !== toolKeys.length) throw new Error('duplicate declared toolchain');\n\n  const body = {\n    contractVersion: 1 as const,\n    assurance: 'declared-unsigned' as const,\n    sourceStateId: requireDigest(sourceState.sourceStateId, 'source state id'),\n    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,","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/build-evidence.ts#L111-L147","documentation":"Toolchain declarations are keyed by the pair name\\0version; declaring the same toolchain name and version twice throws. Two entries with the same name but different versions are fine — the collision is only the exact (name, version) pair, since each pair binds one digest in the hashed evidence.","triggerScenarios":"The same { name: 'node', version: '22.1.0' } declaration appearing in both a preset toolchain list and a user override list that were concatenated; copy-pasted toolchain blocks.","commonSituations":"Layering base and user config without merging toolchain arrays; regenerating declarations by appending instead of upserting; bundling toolchains from multiple build scripts.","solutions":["Deduplicate toolchains by `${name}\\0${version}` before calling createBuildEvidence","To change a toolchain's digest, update the existing (name, version) entry rather than adding a duplicate","Merge toolchain lists with a Map so later entries intentionally replace earlier ones"],"exampleFix":"// before\nconst toolchains = [\n  { name: 'node', version: '22.1.0', path: '/usr/bin/node' },\n  { name: 'node', version: '22.1.0', path: '/usr/local/bin/node' }, // duplicate pair\n];\n\n// after\nconst toolchains = [...new Map(\n  rawTools.map((t) => [`${t.name}\\0${t.version}`, t]),\n).values()];","handlingStrategy":"validation","validationCode":"function dedupeToolchains<T extends { name: string; version: string }>(tools: T[]): T[] {\n  return [...new Map(tools.map((t) => [`${t.name}\\0${t.version}`, t])).values()];\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Key toolchain config by `${name}@${version}` so duplicates are structurally impossible","Layer presets with object spreads keyed by name+version, not array concatenation","Treat a duplicate (name, version) as an upsert, never an append"],"tags":["build-evidence","duplicate","toolchain","validation","harness"],"backgroundTag":"duplicate-config-entry","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}