Egonex-AI/Understand-Anything · error · Error

HEAD changed since prepare; baseline not advanced

Error message

HEAD changed since prepare; baseline not advanced

What it means

`loadSymbolContext` runs `git rev-parse HEAD` and compares it to `plan.headCommit`. If HEAD has moved since the incremental prepare step recorded it, the baseline no longer describes the current working tree and validation would give misleading results, so it throws. The incremental flow assumes nothing advances HEAD between prepare and validate.

Source

Thrown at understand-anything-plugin/skills/understand/validate-incremental-symbols.mjs:304

export function git(root, args) {
  const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
  if (result.status !== 0) throw new Error(`git ${args[0]} failed: ${result.stderr || result.error || result.status}`);
  return result.stdout;
}

export function loadSymbolContext(projectRoot, intermediateDir) {
  const plan = readJson(join(intermediateDir, 'incremental-plan.json'));
  const baseline = readJson(join(intermediateDir, 'incremental-symbol-baseline.json'));
  if (baseline.version !== 1 || baseline.baseCommit !== plan.baseCommit || baseline.headCommit !== plan.headCommit
    || !Array.isArray(baseline.files)) throw new Error('Symbol baseline does not match the incremental plan');
  const paths = baseline.files.map(file => file.filePath).sort();
  if (JSON.stringify(paths) !== JSON.stringify([...plan.filesToReanalyze].sort())
    || paths.some(path => !normalizePath(path) || (plan.deletedFiles ?? []).includes(path))
    || new Set(paths).size !== paths.length) {
    throw new Error('Symbol baseline file inventory does not match the incremental plan');
  }
  if (git(projectRoot, ['rev-parse', 'HEAD']).trim() !== plan.headCommit) {
    throw new Error('HEAD changed since prepare; baseline not advanced');
  }
  // Check every analyzer input, even if all IDs survive and parsing is skipped.
  // Git compares normalized contents, including repository clean/EOL rules.
  if (paths.length) git(projectRoot, [
    'diff', '--quiet', '--no-ext-diff', plan.headCommit, '--', ...paths.map(path => `:(literal)${path}`),
  ]);
  return { plan, baseline };
}

export async function validateIncrementalSymbols(projectRoot, { graph, intermediateDir } = {}) {
  const core = await getCore();
  intermediateDir ??= join(core.resolveUaDir(projectRoot), 'intermediate');
  const reportPath = join(intermediateDir, 'incremental-symbol-report.json');
  const report = { version: 1, ok: false, files: [], unresolvedFiles: [], errors: [] };
  const graphFromDisk = graph === undefined;
  try {
    const { plan, baseline } = loadSymbolContext(projectRoot, intermediateDir);
    report.baseCommit = plan.baseCommit;

View on GitHub (pinned to 07edf82a04)

Solutions

  1. Re-run the incremental prepare step so plan.headCommit and the baseline are recorded against the current HEAD.
  2. Check `git reflog` to see what moved HEAD between prepare and validate.
  3. If a background process (IDE, CI, hook) commits or switches branches, disable it during the incremental run.
  4. Validate promptly after prepare; do not reuse intermediate artifacts across sessions that include new commits.

Example fix

// before: artifacts recorded for an older commit
plan.headCommit = "abc123"; git rev-parse HEAD => "def456"

// after: regenerate artifacts at current HEAD
git checkout abc123  # or
rm .ua/intermediate/incremental-*.json && re-run prepare at HEAD
Defensive patterns

Strategy: validation

Validate before calling

import { execSync } from 'node:child_process';
const plan = JSON.parse(readFileSync(join(dir,'incremental-plan.json'),'utf8'));
const head = execSync('git rev-parse HEAD', { cwd: projectRoot }).toString().trim();
if (head !== plan.headCommit) rerunPrepareStep(); // before validating

Try / catch

try {
  await validateIncrementalSymbols({ projectRoot, intermediateDir });
} catch (err) {
  if (err.message === 'HEAD changed since prepare; baseline not advanced') {
    await rerunPrepareStep(); // re-record plan/baseline at current HEAD
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `loadSymbolContext` (or `validateIncrementalSymbols`) after a commit, checkout, pull/rebase, or branch switch changed HEAD between the prepare step that wrote `plan.headCommit` and the validation run.

Common situations: Another developer/CI pushed and someone pulled mid-run; an IDE auto-committed or ran `git commit` between steps; a hook switched branches; the prepare artifacts were reused hours/days later after new commits.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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