{"record":{"id":"f9fc7af622791286","repo":"pbakaus/impeccable","slug":"manual-edit-buffer-invalid-schema","errorCode":null,"errorMessage":"manual_edit_buffer_invalid_schema","messagePattern":"manual_edit_buffer_invalid_schema","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugin/skills/impeccable/scripts/live/manual-edits-buffer.mjs","lineNumber":38,"sourceCode":"export function getBufferPath(cwd = process.cwd()) {\n  return path.join(getLiveDir(cwd), BUFFER_FILENAME);\n}\n\nexport function readBuffer(cwd = process.cwd()) {\n  return readBufferInternal(cwd, { strict: false });\n}\n\nexport function readBufferStrict(cwd = process.cwd()) {\n  return readBufferInternal(cwd, { strict: true });\n}\n\nfunction readBufferInternal(cwd, { strict }) {\n  const filePath = getBufferPath(cwd);\n  try {\n    const raw = fs.readFileSync(filePath, 'utf-8');\n    const parsed = JSON.parse(raw);\n    if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.entries)) {\n      if (strict) throw new Error('manual_edit_buffer_invalid_schema');\n      return { version: BUFFER_VERSION, entries: [] };\n    }\n    return { version: BUFFER_VERSION, entries: parsed.entries };\n  } catch (err) {\n    if (strict && err?.code !== 'ENOENT') {\n      throw new Error('manual_edit_buffer_unreadable: ' + (err.message || String(err)));\n    }\n    return { version: BUFFER_VERSION, entries: [] };\n  }\n}\n\nexport function writeBuffer(cwd, buffer) {\n  const filePath = getBufferPath(cwd);\n  fs.mkdirSync(path.dirname(filePath), { recursive: true });\n  fs.writeFileSync(filePath, JSON.stringify({ version: BUFFER_VERSION, entries: buffer.entries }, null, 2));\n}\n\n/**","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/plugin/skills/impeccable/scripts/live/manual-edits-buffer.mjs#L20-L56","documentation":"Thrown by readBufferStrict() (strict mode) when the pending-manual-edits buffer file exists and parses as JSON but does not have the shape { entries: [] } — i.e. parsed is not an object or parsed.entries is not an array. The non-strict readBuffer() silently returns an empty buffer instead. stageEntry() calls readBufferStrict() before merging, so staging a Save hits this if the on-disk buffer is corrupted.","triggerScenarios":".impeccable/live/pending-manual-edits.json contains a JSON array, a primitive, or an object without 'entries' (e.g. {\"ops\":[]} after a hand-edit or a partial write). The file exists (ENOENT is handled separately) and JSON.parse succeeds, but the schema check fails. ENOENT does NOT throw even in strict mode — only a malformed-but-present file does.","commonSituations":"A previous write was interrupted mid-file (partial JSON that happens to parse); a tool/manual edit rewrote the buffer with a different schema; migration from an older buffer format where the top-level shape changed; concurrent writers corrupted the array. readBufferStrict is called from stageEntry and live-commit-manual-edits.","solutions":["Stop any live sessions writing to the buffer, delete .impeccable/live/pending-manual-edits.json, and re-stage edits from the browser.","Repair the file to the canonical shape: { \"version\": 1, \"entries\": [] } (or with valid entry objects matching { id, pageUrl, element, ops, stagedAt }).","Use the non-strict readBuffer() to recover the entries you can and writeBuffer() a clean replacement.","If this recurs, investigate concurrent writers — the buffer is project-local and not lock-guarded."],"exampleFix":"// before — .impeccable/live/pending-manual-edits.json\n[{\"id\":\"x\",\"ops\":[]}]\n\n// after\n{\n  \"version\": 1,\n  \"entries\": []\n}\n\n// or delete the file and re-stage from the browser Save action:\n// rm .impeccable/live/pending-manual-edits.json","handlingStrategy":"fallback","validationCode":"import { readBuffer, writeBuffer, getBufferPath } from './live/manual-edits-buffer.mjs';\nimport fs from 'node:fs';\n// Pre-check without throwing: use the non-strict read, then validate.\nfunction safeReadBuffer(cwd) {\n  const buf = readBuffer(cwd); // never throws\n  if (!Array.isArray(buf?.entries)) {\n    // quarantine the corrupt file and reset\n    const p = getBufferPath(cwd);\n    if (fs.existsSync(p)) fs.renameSync(p, p + '.corrupt');\n    writeBuffer(cwd, { version: 1, entries: [] });\n    return { version: 1, entries: [] };\n  }\n  return buf;\n}","typeGuard":"/** True when the parsed buffer matches { entries: [] }. */\nfunction isValidBufferShape(parsed) {\n  return parsed !== null\n    && typeof parsed === 'object'\n    && Array.isArray(parsed.entries);\n}","tryCatchPattern":"import { readBufferStrict } from './live/manual-edits-buffer.mjs';\nlet buf;\ntry {\n  buf = readBufferStrict(cwd);\n} catch (err) {\n  if (/manual_edit_buffer_invalid_schema/.test(err.message)) {\n    // quarantine and reset rather than fail the stage\n    const p = getBufferPath(cwd);\n    if (fs.existsSync(p)) fs.renameSync(p, p + '.corrupt');\n    buf = { version: 1, entries: [] };\n  } else {\n    throw err;\n  }\n}","preventionTips":["Use the non-strict readBuffer() for read-only UI paths; it returns an empty buffer on corruption instead of throwing.","Avoid concurrent writers to pending-manual-edits.json — the buffer has no lock.","If you hand-edit the file, keep the canonical { \"version\": 1, \"entries\": [...] } shape."],"tags":["manual-edits","buffer","json","schema"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}