{"record":{"id":"52906b3a27740436","repo":"Egonex-AI/Understand-Anything","slug":"current-edge-candidates-do-not-match-the-increment","errorCode":null,"errorMessage":"Current edge candidates do not match the incremental plan; rerun merge","messagePattern":"Current edge candidates do not match the incremental plan; rerun merge","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"understand-anything-plugin/skills/understand/prepare-symbol-retry.mjs","lineNumber":74,"sourceCode":"    batch.previousSymbols = baseline.files.filter(file => files.has(file.filePath)).flatMap(file =>\n      file.nodes.filter(symbolKind).map(node => {\n        const parents = new Set(file.edges.filter(edge => edge.type === 'contains' && edge.target === node.id)\n          .map(edge => edge.source));\n        const owners = file.nodes.filter(parent => parent.type === 'class' && parents.has(parent.id))\n          .map(parent => parent.name);\n        return {\n          id: node.id, name: node.name, type: node.type, filePath: node.filePath,\n          ...(node.lineRange ? { lineRange: node.lineRange } : {}),\n          ...(owners.length ? { owners } : {}),\n        };\n      }),\n    );\n    batch.missingSymbols = report.files.filter(file => files.has(file.filePath));\n  }\n  const assembled = readJson(join(intermediateDir, 'assembled-graph.json'));\n  const candidates = readJson(join(intermediateDir, 'incremental-edge-candidates.json'));\n  if (candidates.baseCommit !== plan.baseCommit || candidates.headCommit !== plan.headCommit\n    || !Array.isArray(candidates.edges)) throw new Error('Current edge candidates do not match the incremental plan; rerun merge');\n  const removedIds = new Set(assembled.nodes.filter(node => paths.has(normalizePath(node.filePath)))\n    .map(node => node.id));\n  const retainedIds = new Set(assembled.nodes.filter(node => !removedIds.has(node.id)).map(node => node.id));\n  const currentNodes = new Map(assembled.nodes.map(node => [node.id, node]));\n  const previousIds = new Set(baseline.files.filter(file => paths.has(file.filePath))\n    .flatMap(file => file.nodes.map(node => node.id)));\n  const targetsAffectedFile = id => removedIds.has(id) || previousIds.has(id)\n    || (typeof id === 'string' && [...paths].some(path => id.includes(`:${path}:`)));\n  const sourceRemap = new Map(report.files.flatMap(file => file.replacements)\n    .map(({ oldId, newId }) => [oldId, newId]));\n  const inboundEdgeCandidates = [...assembled.edges, ...candidates.edges]\n    .map(edge => ({ ...edge, source: currentNodes.has(edge.source) ? edge.source : sourceRemap.get(edge.source) ?? edge.source })).filter(edge =>\n    retainedIds.has(edge.source) && targetsAffectedFile(edge.target));\n  const contextPaths = new Set(paths);\n  for (const edge of inboundEdgeCandidates) {\n    for (const id of [edge.source, edge.target]) {\n      if (currentNodes.has(id)) contextPaths.add(normalizePath(currentNodes.get(id).filePath));\n    }","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/Egonex-AI/Understand-Anything/blob/07edf82a04371b6f69779b067bdc8a1a8753a9db/understand-anything-plugin/skills/understand/prepare-symbol-retry.mjs#L56-L92","documentation":"Before computing removed/retained nodes for the retry, the script checks that the persisted incremental-edge-candidates.json matches the current plan's baseCommit and headCommit and contains an edges array. A mismatch means the candidates file is from a different (older) incremental run, so it throws and instructs re-running the merge to regenerate candidates in sync with the plan.","triggerScenarios":"Running prepare-symbol-retry.mjs when incremental-edge-candidates.json was written for a different base/head commit pair than the loaded plan, was not produced by the merge step, or is missing the edges array (older schema or partial write).","commonSituations":"Leftover candidates file from a previous incremental run after commits advanced; interrupted merge step that never wrote candidates for the current plan; hand-copied intermediate state between machines/branches; older plugin version writing a different candidates schema.","solutions":["Re-run the incremental merge step so incremental-edge-candidates.json is regenerated for the current base/head commits.","Clear stale intermediate files (incremental-edge-candidates.json) and re-run prepare-incremental.mjs followed by prepare-symbol-retry.mjs.","Verify you are operating on the same commits as the plan — if HEAD moved, re-prepare the incremental plan first.","Check plugin version consistency: old candidates schema (missing edges array) requires regenerating with the current version."],"exampleFix":"// before: candidates from older run\n// candidates: {baseCommit:'aaa', headCommit:'bbb'}, plan: {baseCommit:'aaa', headCommit:'ccc'} → throw\n// after: regenerate candidates via merge\nnode prepare-incremental.mjs .   # rewrites incremental-edge-candidates.json for current plan\nnode prepare-symbol-retry.mjs .","handlingStrategy":"validation","validationCode":"import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nconst candidatesPath = join(intermediateDir, 'incremental-edge-candidates.json');\nif (!existsSync(candidatesPath)) throw new Error('Edge candidates missing; rerun merge');\nconst c = JSON.parse(readFileSync(candidatesPath, 'utf-8'));\nif (c.baseCommit !== plan.baseCommit || c.headCommit !== plan.headCommit || !Array.isArray(c.edges)) {\n  throw new Error('Stale edge candidates; rerun incremental merge first');\n}","typeGuard":"const candidatesMatchPlan = (candidates, plan) =>\n  candidates && candidates.baseCommit === plan.baseCommit\n  && candidates.headCommit === plan.headCommit\n  && Array.isArray(candidates.edges);","tryCatchPattern":"try {\n  await runRetry(projectRoot);\n} catch (e) {\n  if (e.message.includes('Current edge candidates do not match')) {\n    // rerun prepare-incremental.mjs to regenerate candidates for the current plan\n  } else throw e;\n}","preventionTips":["Always run the incremental merge step before symbol retry so candidates match the plan.","Clear stale intermediate files after commits advance or branches change.","Record base/head commits in all intermediate artifacts and validate them together.","Don't copy intermediate directories between machines or branches."],"tags":["stale-state","intermediate-files","incremental-update","invariant-violation"],"backgroundTag":"stale-cache-mismatch","analyzedSha":"07edf82a04371b6f69779b067bdc8a1a8753a9db","analyzedAt":"2026-09-07T23:20:10.829Z","contentChangedAt":"2026-09-07T23:20:10.829Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}