pbakaus/impeccable · error · Error

manual_edit_buffer_invalid_schema

Error message

manual_edit_buffer_invalid_schema

What it means

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.

Source

Thrown at plugin/skills/impeccable/scripts/live/manual-edits-buffer.mjs:38

export function getBufferPath(cwd = process.cwd()) {
  return path.join(getLiveDir(cwd), BUFFER_FILENAME);
}

export function readBuffer(cwd = process.cwd()) {
  return readBufferInternal(cwd, { strict: false });
}

export function readBufferStrict(cwd = process.cwd()) {
  return readBufferInternal(cwd, { strict: true });
}

function readBufferInternal(cwd, { strict }) {
  const filePath = getBufferPath(cwd);
  try {
    const raw = fs.readFileSync(filePath, 'utf-8');
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.entries)) {
      if (strict) throw new Error('manual_edit_buffer_invalid_schema');
      return { version: BUFFER_VERSION, entries: [] };
    }
    return { version: BUFFER_VERSION, entries: parsed.entries };
  } catch (err) {
    if (strict && err?.code !== 'ENOENT') {
      throw new Error('manual_edit_buffer_unreadable: ' + (err.message || String(err)));
    }
    return { version: BUFFER_VERSION, entries: [] };
  }
}

export function writeBuffer(cwd, buffer) {
  const filePath = getBufferPath(cwd);
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, JSON.stringify({ version: BUFFER_VERSION, entries: buffer.entries }, null, 2));
}

/**

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Stop any live sessions writing to the buffer, delete .impeccable/live/pending-manual-edits.json, and re-stage edits from the browser.
  2. Repair the file to the canonical shape: { "version": 1, "entries": [] } (or with valid entry objects matching { id, pageUrl, element, ops, stagedAt }).
  3. Use the non-strict readBuffer() to recover the entries you can and writeBuffer() a clean replacement.
  4. If this recurs, investigate concurrent writers — the buffer is project-local and not lock-guarded.

Example fix

// before — .impeccable/live/pending-manual-edits.json
[{"id":"x","ops":[]}]

// after
{
  "version": 1,
  "entries": []
}

// or delete the file and re-stage from the browser Save action:
// rm .impeccable/live/pending-manual-edits.json
Defensive patterns

Strategy: fallback

Validate before calling

import { readBuffer, writeBuffer, getBufferPath } from './live/manual-edits-buffer.mjs';
import fs from 'node:fs';
// Pre-check without throwing: use the non-strict read, then validate.
function safeReadBuffer(cwd) {
  const buf = readBuffer(cwd); // never throws
  if (!Array.isArray(buf?.entries)) {
    // quarantine the corrupt file and reset
    const p = getBufferPath(cwd);
    if (fs.existsSync(p)) fs.renameSync(p, p + '.corrupt');
    writeBuffer(cwd, { version: 1, entries: [] });
    return { version: 1, entries: [] };
  }
  return buf;
}

Type guard

/** True when the parsed buffer matches { entries: [] }. */
function isValidBufferShape(parsed) {
  return parsed !== null
    && typeof parsed === 'object'
    && Array.isArray(parsed.entries);
}

Try / catch

import { readBufferStrict } from './live/manual-edits-buffer.mjs';
let buf;
try {
  buf = readBufferStrict(cwd);
} catch (err) {
  if (/manual_edit_buffer_invalid_schema/.test(err.message)) {
    // quarantine and reset rather than fail the stage
    const p = getBufferPath(cwd);
    if (fs.existsSync(p)) fs.renameSync(p, p + '.corrupt');
    buf = { version: 1, entries: [] };
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: .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.

Common situations: 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.

Related errors


AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13). Data as JSON: /api/errors/f9fc7af622791286. Report an issue: GitHub.