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
- Ensure only one process writes to a given vault directory at a time (serialize with a lock or queue).
- On Windows, upgrade Node to a version with libuv >= 1.51.0 (22.17+, 24.2+).
- Disable or exclude the vault directory from temp-cleaning and sync tools that may touch .ecc-memory-*.tmp files.
- 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
- Serialize writes to a vault directory with a lock or queue.
- Exclude .ecc-memory-*.tmp files from janitors, sync tools, and antivirus.
- On Windows, run Node 22.17+ / 24.2+ (libuv 1.51.0+).
- Treat recurring 'destination changed' errors as a concurrency or security-tool signal.
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
- ${label} must remain a regular, non-symlink file while it is
- Refusing to access memory through symlink root: ${root}
- Refusing to access memory through symlink directory: ${direc
- ${label} is too large (maximum ${maxBytes} bytes).
- Refusing to ${action}: destination parent is not a trusted d
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/c04bb392613e3975.
Report an issue: GitHub.