affaan-m/ECC · error · Error
Memory ${memory.id} already exists; writes are create-only.
Error message
Memory ${memory.id} already exists; writes are create-only. What it means
Vault writes are create-only: writeCreateOnlyTextFile opens the destination with O_CREAT|O_EXCL, so it fails with EEXIST if the file is already present. saveMemory translates that EEXIST into an explicit 'writes are create-only' error, refusing to overwrite an existing memory. This makes memory IDs immutable-once-written and prevents accidental clobbering of audit history.
Source
Thrown at scripts/lib/memory-vault.js:339
const memory = normalizeSaveInput(input || {}, options);
const secretKinds = findPotentialSecrets(JSON.stringify(memory));
if (secretKinds.length > 0) {
throw new Error(`Refusing to save memory containing a suspected secret (${secretKinds.join(', ')}).`);
}
const root = assertMemoryRootSafe(roots, memory.scope);
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
ensureProjectScopeIgnored(roots, memory.scope);
const directory = path.join(root, `${memory.kind}s`);
assertMemoryDirectorySafe(directory, root);
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
const destination = path.join(directory, `${memory.id}.md`);
try {
writeCreateOnlyTextFile(destination, serializeMemoryDocument(memory), root);
} catch (error) {
if (error && error.code === 'EEXIST') {
throw new Error(`Memory ${memory.id} already exists; writes are create-only.`);
}
throw error;
}
return { memory, path: destination };
}
function walkMemoryRoot(root, maxEntries = MAX_FILES) {
if (!root || !fs.existsSync(root)) {
return {
paths: [],
skippedSymlinks: [],
skippedSymlinkCount: 0,
truncated: false,
visitedCount: 0,
};
}
const paths = [];View on GitHub (pinned to 01e15490f0)
Solutions
- Omit input.id so the vault generates a fresh unique id (defaultMemoryId uses date + random UUID suffix).
- If you must use a stable id and the existing memory is wrong, delete the old file first (rm <root>/<kind>s/<id>.md) and re-save — but treat this as editing history, not updating in place.
- If using a custom idFactory, ensure it cannot return an id already present on disk (check fs.existsSync or use crypto.randomUUID).
- For 'upsert' semantics, read the existing memory, then save a new memory with a new id and a link to the old one, rather than trying to overwrite.
Example fix
// before
saveMemory({ id: 'mem_notes', title: 'x', body: '...' }); // second call fails
// after: let the vault mint a unique id
saveMemory({ title: 'x', body: '...' }); // id auto-generated as mem_YYYYMMDD_<random> Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
const path = require('path');
function memoryIdIsFree(roots, scope, kind, id) {
const dest = path.join(roots[scope], `${kind}s`, `${id}.md`);
return !fs.existsSync(dest);
}
// before saveMemory with an explicit id:
if (!memoryIdIsFree(roots, scope, kind, input.id)) {
throw new Error(`Memory ${input.id} already exists; writes are create-only.`);
} Try / catch
try { saveMemory(input); }
catch (error) {
if (/writes are create-only/.test(error.message)) {
// generate a fresh id and retry
return saveMemory({ ...input, id: undefined });
}
throw error;
} Prevention
- Omit input.id whenever possible so the vault mints a unique id.
- If using a custom idFactory, base it on crypto.randomUUID to avoid collisions.
- For 'update' semantics, save a new memory with a link to the old one rather than overwriting.
- Before re-saving after an error, regenerate the id.
When it happens
Trigger: Calling saveMemory with an input.id (or a generated id from a custom idFactory) that already names a file on disk under <root>/<kind>s/<id>.md. Also possible after a retry that did not change the id, or when an idFactory produces collisions.
Common situations: Retrying a failed save without regenerating the id; passing a deterministic id that already exists; an idFactory based on a hash of content colliding; a partial prior write that left the file in place; manually crafting an id that conflicts.
Related errors
- ${label} must be a regular, non-symlink file.
- ${label} is too large (${opened.size} bytes).
- Multiple ECC repo roots detected: ${uniqueRepoRoots.join(',
- TypeScript compiler not found. Install root dev dependencies
- Missing value for --family
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/20a151bc96869293.
Report an issue: GitHub.