{"record":{"id":"32b3ecbc4eb2dad6","repo":"Egonex-AI/Understand-Anything","slug":"head-changed-since-prepare-baseline-not-advanced","errorCode":null,"errorMessage":"HEAD changed since prepare; baseline not advanced","messagePattern":"HEAD changed since prepare; baseline not advanced","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"understand-anything-plugin/skills/understand/validate-incremental-symbols.mjs","lineNumber":304,"sourceCode":"export function git(root, args) {\n  const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });\n  if (result.status !== 0) throw new Error(`git ${args[0]} failed: ${result.stderr || result.error || result.status}`);\n  return result.stdout;\n}\n\nexport function loadSymbolContext(projectRoot, intermediateDir) {\n  const plan = readJson(join(intermediateDir, 'incremental-plan.json'));\n  const baseline = readJson(join(intermediateDir, 'incremental-symbol-baseline.json'));\n  if (baseline.version !== 1 || baseline.baseCommit !== plan.baseCommit || baseline.headCommit !== plan.headCommit\n    || !Array.isArray(baseline.files)) throw new Error('Symbol baseline does not match the incremental plan');\n  const paths = baseline.files.map(file => file.filePath).sort();\n  if (JSON.stringify(paths) !== JSON.stringify([...plan.filesToReanalyze].sort())\n    || paths.some(path => !normalizePath(path) || (plan.deletedFiles ?? []).includes(path))\n    || new Set(paths).size !== paths.length) {\n    throw new Error('Symbol baseline file inventory does not match the incremental plan');\n  }\n  if (git(projectRoot, ['rev-parse', 'HEAD']).trim() !== plan.headCommit) {\n    throw new Error('HEAD changed since prepare; baseline not advanced');\n  }\n  // Check every analyzer input, even if all IDs survive and parsing is skipped.\n  // Git compares normalized contents, including repository clean/EOL rules.\n  if (paths.length) git(projectRoot, [\n    'diff', '--quiet', '--no-ext-diff', plan.headCommit, '--', ...paths.map(path => `:(literal)${path}`),\n  ]);\n  return { plan, baseline };\n}\n\nexport async function validateIncrementalSymbols(projectRoot, { graph, intermediateDir } = {}) {\n  const core = await getCore();\n  intermediateDir ??= join(core.resolveUaDir(projectRoot), 'intermediate');\n  const reportPath = join(intermediateDir, 'incremental-symbol-report.json');\n  const report = { version: 1, ok: false, files: [], unresolvedFiles: [], errors: [] };\n  const graphFromDisk = graph === undefined;\n  try {\n    const { plan, baseline } = loadSymbolContext(projectRoot, intermediateDir);\n    report.baseCommit = plan.baseCommit;","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/Egonex-AI/Understand-Anything/blob/07edf82a04371b6f69779b067bdc8a1a8753a9db/understand-anything-plugin/skills/understand/validate-incremental-symbols.mjs#L286-L322","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: artifacts recorded for an older commit\nplan.headCommit = \"abc123\"; git rev-parse HEAD => \"def456\"\n\n// after: regenerate artifacts at current HEAD\ngit checkout abc123  # or\nrm .ua/intermediate/incremental-*.json && re-run prepare at HEAD","handlingStrategy":"validation","validationCode":"import { execSync } from 'node:child_process';\nconst plan = JSON.parse(readFileSync(join(dir,'incremental-plan.json'),'utf8'));\nconst head = execSync('git rev-parse HEAD', { cwd: projectRoot }).toString().trim();\nif (head !== plan.headCommit) rerunPrepareStep(); // before validating","typeGuard":null,"tryCatchPattern":"try {\n  await validateIncrementalSymbols({ projectRoot, intermediateDir });\n} catch (err) {\n  if (err.message === 'HEAD changed since prepare; baseline not advanced') {\n    await rerunPrepareStep(); // re-record plan/baseline at current HEAD\n  } else throw err;\n}","preventionTips":["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."],"tags":["git","stale-state","incremental-analysis"],"backgroundTag":"invalid-state-transition","analyzedSha":"07edf82a04371b6f69779b067bdc8a1a8753a9db","analyzedAt":"2026-09-07T23:20:10.829Z","contentChangedAt":"2026-09-07T23:20:10.829Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}