affaan-m/ECC · error · Error

Memory destination changed while it was being created.

Error message

Memory destination changed while it was being created.

What it means

writeCreateOnlyTextFile opens a unique temp file with O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, then re-validates with fstat and lstat that the temp file is a regular file with matching identity before writing. If the just-created temp file is not a regular file, has become a symlink, or its inode/dev no longer matches, the write is aborted as a suspected swap. This is the write-side counterpart of the read TOCTOU guard.

Source

Thrown at scripts/lib/memory-vault.js:215

  const flags = fs.constants.O_WRONLY
    | fs.constants.O_CREAT
    | fs.constants.O_EXCL
    | (fs.constants.O_NOFOLLOW || 0);
  let descriptor;
  let operationError;
  let cleanupError;
  try {
    descriptor = fs.openSync(temporaryPath, flags, 0o600);
    const opened = fs.fstatSync(descriptor, { bigint: true });
    const after = fs.lstatSync(temporaryPath, { bigint: true });
    assertWithinTrustedRoot(temporaryPath, trustedRoot, 'write memory');
    if (
      !opened.isFile()
      || after.isSymbolicLink()
      || !after.isFile()
      || !sameFileIdentity(after, opened)
    ) {
      throw new Error('Memory destination changed while it was being created.');
    }
    fs.writeFileSync(descriptor, content, 'utf8');
    fs.fsyncSync(descriptor);
    fs.closeSync(descriptor);
    descriptor = undefined;
    assertWithinTrustedRoot(filePath, trustedRoot, 'write memory');
    fs.linkSync(temporaryPath, filePath);
  } catch (error) {
    operationError = error;
  } finally {
    if (descriptor !== undefined) {
      try {
        fs.closeSync(descriptor);
      } catch (error) {
        cleanupError = error;
      }
    }
    try {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Ensure only one process writes to a given vault directory at a time (serialize with a lock or queue).
  2. On Windows, upgrade Node to a version with libuv >= 1.51.0 (22.17+, 24.2+).
  3. Disable or exclude the vault directory from temp-cleaning and sync tools that may touch .ecc-memory-*.tmp files.
  4. Retry the save once after the race clears; transient races on temp creation are usually self-correcting on the next attempt.

Example fix

// before: janitor removes .ecc-memory-*.tmp mid-write
// exclude the vault from the cleaner, then retry
await saveMemory(input);
// after: temp file survives the open->fstat window, write succeeds
Defensive patterns

Strategy: retry

Try / catch

async function saveStable(input, options, retries = 2) {
  for (let i = 0; i <= retries; i++) {
    try { return saveMemory(input, options); }
    catch (error) {
      if (/destination changed while it was being created/i.test(error.message) && i < retries) continue;
      throw error;
    }
  }
}

Prevention

When it happens

Trigger: Between openSync(O_EXCL) of the temp file and the post-open fstat/lstat, the temp file was replaced, symlinked, or its inode changed. Realistic causes: a concurrent cleanup process deleting and recreating temp files, a sync tool touching .ecc-memory-*.tmp, an antivirus quarantining the temp, or the Windows libuv volume-serial mismatch on Node 22.12–22.16 / 24.0–24.1.

Common situations: Two saveMemory calls racing into the same directory; a janitor script that nukes .tmp files; security software scanning and recreating temp files; the affected libuv/Windows version range where dev mismatches break sameFileIdentity.

Related errors


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