Egonex-AI/Understand-Anything · error · Error
Current edge candidates do not match the incremental plan
Error message
Current edge candidates do not match the incremental plan
What it means
During validation, `validateIncrementalSymbols` reads the optional `incremental-edge-candidates.json` and requires it to match the current plan: identical `baseCommit`, identical `headCommit`, and an `edges` array. A candidates file recorded for a different commit range is stale, so it throws rather than mixing edge candidates across runs.
Source
Thrown at understand-anything-plugin/skills/understand/validate-incremental-symbols.mjs:382
} catch (error) {
baseEvidence = { status: 'failed' };
headEvidence = { status: 'failed' };
report.errors.push(`${previous.filePath}: ${error.message}`);
}
}
const result = compareFileSymbols(previous, current, baseEvidence, headEvidence);
report.files.push(result);
if (result.missing.some(node => node.status !== 'deleted')) report.unresolvedFiles.push(previous.filePath);
}
report.ok = report.errors.length === 0 && report.unresolvedFiles.length === 0;
if (report.ok) {
const retryPath = join(intermediateDir, 'incremental-symbol-retry.json');
const retry = existsSync(retryPath) ? readJson(retryPath) : null;
const candidatePath = join(intermediateDir, 'incremental-edge-candidates.json');
const currentCandidates = existsSync(candidatePath) ? readJson(candidatePath) : null;
if (currentCandidates && (currentCandidates.baseCommit !== plan.baseCommit
|| currentCandidates.headCommit !== plan.headCommit || !Array.isArray(currentCandidates.edges))) {
throw new Error('Current edge candidates do not match the incremental plan');
}
const hasRetry = retry?.baseCommit === plan.baseCommit && retry.headCommit === plan.headCommit
&& Array.isArray(retry.inboundEdgeCandidates);
if (hasRetry && !Array.isArray(retry.currentFiles)) throw new Error('Retry endpoint descriptors are missing');
const candidates = [
...(currentCandidates?.edges ?? []).map(edge => ({ edge, saved: false })),
...(hasRetry ? retry.inboundEdgeCandidates : []).map(edge => ({ edge, saved: true })),
];
if (candidates.length || hasRetry) {
const ids = new Set(graph.nodes.map(node => node.id));
const replacements = new Map(report.files.flatMap(file => file.replacements)
.map(({ oldId, newId }) => [oldId, newId]));
const deleted = new Set(report.files.flatMap(file => file.missing.map(node => node.id)));
const baselineBindings = new Map(baseline.files.flatMap(file => file.nodes.filter(symbolKind))
.map(node => [node.id, deleted.has(node.id) ? null : replacements.get(node.id) ?? node.id]));
const currentBindings = new Map();
// These descriptors belong to the initial CURRENT analysis, not the
// old published graph. Both sides therefore map against HEAD source.View on GitHub (pinned to 07edf82a04)
Solutions
- Delete the stale `incremental-edge-candidates.json` (and retry file) and re-run the pipeline step that regenerates it for the current plan.
- Confirm the candidates file's `baseCommit`/`headCommit` equal the plan's (`jq '.baseCommit,.headCommit' incremental-edge-candidates.json`).
- If edges were lost, re-run edge candidate extraction so `edges` is a populated array.
- Clean the whole intermediate directory when switching branches or after a rebase.
Example fix
// before: stale candidates from a previous head
incremental-plan.json: { headCommit: "def456" }
incremental-edge-candidates.json: { headCommit: "abc123", edges: [...] }
// after: regenerate for the current plan
rm .ua/intermediate/incremental-edge-candidates.json .ua/intermediate/incremental-symbol-retry.json
# re-run edge candidate extraction at headCommit def456 Defensive patterns
Strategy: validation
Validate before calling
const plan = JSON.parse(readFileSync(join(dir,'incremental-plan.json'),'utf8'));
const cPath = join(dir,'incremental-edge-candidates.json');
if (existsSync(cPath)) {
const c = JSON.parse(readFileSync(cPath,'utf8'));
const ok = c.baseCommit === plan.baseCommit && c.headCommit === plan.headCommit
&& Array.isArray(c.edges);
if (!ok) { unlinkSync(cPath); await regenerateEdgeCandidates(); }
} Type guard
function candidatesMatchPlan(plan, c) {
return c != null && c.baseCommit === plan.baseCommit
&& c.headCommit === plan.headCommit && Array.isArray(c.edges);
} Try / catch
try {
await validateIncrementalSymbols({ projectRoot, intermediateDir });
} catch (err) {
if (err.message === 'Current edge candidates do not match the incremental plan') {
rmSync(join(intermediateDir, 'incremental-edge-candidates.json'));
await rerunPrepareAndEdgeExtraction();
} else throw err;
} Prevention
- Regenerate edge candidates whenever the plan's commits change (rebase, new commits).
- Clean intermediate/ when switching branches.
- Compare baseCommit/headCommit of all intermediate files before validating.
- Write candidates files atomically to avoid missing edges arrays.
When it happens
Trigger: Calling `validateIncrementalSymbols` when a leftover `incremental-edge-candidates.json` was written by a run with a different baseCommit/headCommit than `incremental-plan.json`, or when the file exists but its `edges` field is not an array.
Common situations: Re-running validation after new commits without regenerating edge candidates; rebase amended the head commit so the recorded headCommit no longer matches; a partially written candidates file lost its edges array; reusing an intermediate directory across branches.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- HEAD changed since prepare; baseline not advanced
- Fingerprint patch does not match the incremental plan commit
- Architecture update requires layers.json; baseline not advan
- Architecture update requires tour.json; baseline not advance
- Unresolved incremental symbol loss; baseline not advanced
AI-assisted analysis of Egonex-AI/Understand-Anything@07edf82a04 (2026-09-07).
Data as JSON: /api/errors/4f25f18506e3a106.
Report an issue: GitHub.