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

  1. Delete the stale `incremental-edge-candidates.json` (and retry file) and re-run the pipeline step that regenerates it for the current plan.
  2. Confirm the candidates file's `baseCommit`/`headCommit` equal the plan's (`jq '.baseCommit,.headCommit' incremental-edge-candidates.json`).
  3. If edges were lost, re-run edge candidate extraction so `edges` is a populated array.
  4. 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

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


AI-assisted analysis of Egonex-AI/Understand-Anything@07edf82a04 (2026-09-07). Data as JSON: /api/errors/4f25f18506e3a106. Report an issue: GitHub.