affaan-m/ECC · error · Error
Memory document must start with --- frontmatter.
Error message
Memory document ${sourcePath} must start with --- frontmatter. What it means
parseMemoryDocument() requires a memory document to begin with the `---` frontmatter opening marker (for string sources; buffer sources bypass this check). The frontmatter-delimited format is the document's structural contract, so a file missing the opening fence is rejected before any field parsing.
Solutions
- Start the document with `---` on the very first line, followed by frontmatter and a closing `---`.
- Remove any leading blank lines, BOM, or preamble text before the opening fence.
- Ensure the fence is exactly three dashes on its own line (no leading spaces, no extra characters).
- If the file isn't a memory document, move it out of the memory vault / don't pass it to parseMemoryDocument.
Example fix
// before (missing fences) id: "notes" title: "Deploy notes" // after --- id: "notes" title: "Deploy notes" --- Body text here.
Defensive patterns
Strategy: validation
Validate before calling
const src = fs.readFileSync(path, 'utf8').replace(/^\uFEFF/, '');
if (!/^---(\r?\n|$)/.test(src)) throw new Error(`${path} is missing the opening --- frontmatter fence`); Type guard
const hasFrontmatter = (s) => typeof s === 'string' && /^---\r?\n/.test(s.replace(/^\uFEFF/, ''));
Try / catch
try {
parseMemoryDocument(source, path);
} catch (err) {
if (err.message.includes('must start with --- frontmatter')) {
console.error('%s lacks the leading --- fence; add frontmatter or move the file out of the vault.', path);
} else throw err;
} Prevention
- Every memory file must begin with `---` as the very first line.
- Strip BOMs and leading blank lines when generating files programmatically.
- Keep notes and non-memory files out of the memory vault directory.
When it happens
Trigger: Loading a memory file that starts with blank lines, a BOM, prose/markdown text, a different fence style (e.g. `--- ` with trailing content on the same line, `---\r` handled ok but `----` not), or a completely empty file; passing a string that was stripped of frontmatter.
Common situations: Creating a memory file by hand and forgetting the --- fences; a formatter or linter stripping/altering the leading fence; writing notes without frontmatter into the vault directory and expecting them to load; file begins with a UTF-8 BOM that breaks the ^--- regex match.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid memory frontmatter line in
- Duplicate memory frontmatter field in
- Memory frontmatter field in
- Unknown memory frontmatter field in
- bad timecode
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/b894bbf2d5fb73c7.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/memory-vault-format.js:248
throw new Error(`Unknown memory frontmatter field in ${sourcePath}.`);
}
if (seen.has(objectKey)) {
throw new Error(`Duplicate memory frontmatter field in ${sourcePath}.`);
}
const rawValue = line.slice(separator + 1).trim();
try {
return { objectKey, value: JSON.parse(rawValue) };
} catch {
throw new Error(`Memory frontmatter field in ${sourcePath} must use a JSON value.`);
}
}
function parseMemoryDocument(source, sourcePath = '<memory>') {
const openingMarker = typeof source === 'string'
? /^---\r?\n/.exec(source)
: null;
if (!openingMarker) {
throw new Error(`Memory document ${sourcePath} must start with --- frontmatter.`);
}
if (Buffer.byteLength(source, 'utf8') > MAX_DOCUMENT_BYTES) {
throw new Error(`Memory document ${sourcePath} is too large.`);
}
const frontmatterStart = openingMarker[0].length;
const remainder = source.slice(frontmatterStart);
const closingMarker = /\r?\n---(?=\r?\n|$)/.exec(remainder);
if (!closingMarker) {
throw new Error(`Memory document ${sourcePath} has no closing frontmatter marker.`);
}
const frontmatterSource = remainder.slice(0, closingMarker.index);
const parsed = frontmatterSource.split(/\r?\n/).reduce((state, line) => {
const next = parseFrontmatterLine(line, sourcePath, state.seen);
return {
values: { ...state.values, [next.objectKey]: next.value },
seen: new Set([...state.seen, next.objectKey]),View on GitHub (pinned to 8321021c54)