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
- Re-run the incremental prepare step so plan.headCommit and the baseline are recorded against the current HEAD.
- Check `git reflog` to see what moved HEAD between prepare and validate.
- If a background process (IDE, CI, hook) commits or switches branches, disable it during the incremental run.
- 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
- Run prepare and validate back-to-back without commits, checkouts, or pulls in between.
- Disable IDE auto-commit/branch hooks during incremental runs.
- Check git reflog when validation reports HEAD drift.
- Never cache intermediate artifacts across sessions that include new commits.
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
- Fingerprint patch does not match the incremental plan commit
- Previous graph commit does not match the requested base and
- git ${args[0]} failed: ${result.stderr || result.error || re
- Current edge candidates do not match the incremental plan
- Architecture update requires layers.json; baseline not advan
AI-assisted analysis of Egonex-AI/Understand-Anything@07edf82a04 (2026-09-07).
Data as JSON: /api/errors/32b3ecbc4eb2dad6.
Report an issue: GitHub.