pbakaus/impeccable · error · Error
manual_edit_buffer_unreadable: ${err.message || String(err)}
Error message
manual_edit_buffer_unreadable: ${err.message || String(err)} What it means
Thrown by readBufferStrict when the manual edits buffer file (.impeccable/live/pending-manual-edits.json) exists but cannot be read or parsed, and only in strict mode (ENOENT is tolerated even in strict). The library throws because staging a new manual edit requires a sound baseline to merge against, so it refuses to proceed over corrupt state rather than silently dropping buffered edits.
Source
Thrown at plugin/skills/impeccable/scripts/live/manual-edits-buffer.mjs:44
}
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));
}
/**
* Merge a new entry into the buffer. For each op in the new entry, if there's
* already a buffered op for the same (pageUrl, ref), update that op's newText
* and keep its original originalText (the true source state). Otherwise add
* the op (creating an entry if needed).
*
* Multiple ops in one Save are allowed; each is keyed by (pageUrl, ref).View on GitHub (pinned to d14711ae3d)
Solutions
- Inspect .impeccable/live/pending-manual-edits.json for syntax errors and fix or delete it (deleting loses only pending unstaged manual edits).
- If the file is unneeded, remove it so the next read treats the missing file as empty (ENOENT is tolerated).
- Ensure the directory is writable and not locked by another process, then retry the staging operation.
Example fix
// before: file is corrupt and stageEntry throws
await stageEntry(cwd, newEntry);
// after: validate/repair before staging
import { readBufferStrict } from './manual-edits-buffer.mjs';
try {
readBufferStrict(cwd);
} catch {
fs.rmSync(getBufferPath(cwd), { force: true }); // drop corrupt buffer
}
await stageEntry(cwd, newEntry); Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'node:fs';
function isBufferReadable(filePath) {
if (!fs.existsSync(filePath)) return true; // ENOENT tolerated
try { JSON.parse(fs.readFileSync(filePath, 'utf-8')); return true; }
catch { return false; }
} Type guard
function isValidBuffer(parsed) {
return parsed !== null && typeof parsed === 'object' && Array.isArray(parsed.entries);
} Try / catch
try {
readBufferStrict(cwd);
} catch (err) {
if (err.message.startsWith('manual_edit_buffer_unreadable')) {
fs.rmSync(getBufferPath(cwd), { force: true });
} else throw err;
} Prevention
- Treat the buffer as a derived artifact: never hand-edit pending-manual-edits.json.
- Write buffers atomically (temp file + rename) so a crash cannot leave a partial file.
- Call readBuffer (non-strict) for tolerant reads and reserve readBufferStrict for staging, where a corrupt baseline must halt.
When it happens
Trigger: Calling readBufferStrict(cwd) (or stageEntry, which calls it) when the buffer file is present but contains malformed JSON, or when a filesystem permission error blocks readFileSync. ENOENT is explicitly excluded so a missing file is fine.
Common situations: The buffer was hand-edited or partially written by a crashed process; an external tool overwrote the file with invalid JSON; a stale buffer left over from an aborted Save sits in .impeccable/live/.
Related errors
- entry is not object
- writeSnapshot requires a slug
- Browser script not found at ${browserScriptPath}
- config.json must be an object
- config.files (non-empty string array) required
AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13).
Data as JSON: /api/errors/f586ff445ce569f7.
Report an issue: GitHub.