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

  1. Omit input.id so the vault generates a fresh unique id (defaultMemoryId uses date + random UUID suffix).
  2. 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.
  3. If using a custom idFactory, ensure it cannot return an id already present on disk (check fs.existsSync or use crypto.randomUUID).
  4. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/20a151bc96869293. Report an issue: GitHub.