This error occurs when fs.copyFile() is called with the COPYFILE_EXCL flag and the destination file already exists. The COPYFILE_EXCL flag is a safety mechanism that prevents overwriting existing files, causing the operation to fail if the destination path is occupied.
The EEXIST error with fs.copyFile and COPYFILE_EXCL indicates that a file copy operation failed because the destination file already exists and you explicitly specified that it should not be overwritten. The COPYFILE_EXCL flag (constants.COPYFILE_EXCL) is a safety feature in Node.js that makes fs.copyFile() fail rather than silently overwrite the destination. This is useful when you want to ensure you never accidentally replace an existing file. However, this strict behavior means you must handle the case where the destination already exists. This error is particularly common in scenarios like file uploads where the same filename might be copied multiple times, data migration scripts that run repeatedly, or backup systems where destination files may already exist from previous operations.
The simplest solution is to use fs.copyFile() without the COPYFILE_EXCL flag. This allows the operation to overwrite existing files:
const fs = require('fs').promises;
async function copyFile(src, dest) {
// Without COPYFILE_EXCL - will overwrite if dest exists
await fs.copyFile(src, dest);
console.log('File copied successfully');
}
// Or with callback style
const fs = require('fs');
fs.copyFile('source.txt', 'destination.txt', (err) => {
if (err) throw err;
console.log('Copy completed');
});Use this approach when you don't care if the destination file already exists or should be replaced.
If you need to use COPYFILE_EXCL for safety, remove the destination file first if it exists:
const fs = require('fs').promises;
const { COPYFILE_EXCL } = require('fs').constants;
async function safeCopyFile(src, dest) {
try {
// Try to remove destination if it exists
await fs.unlink(dest);
} catch (err) {
if (err.code !== 'ENOENT') {
// If error is not "file not found", re-throw
throw err;
}
// File doesn't exist, which is fine
}
// Now copy with COPYFILE_EXCL flag
await fs.copyFile(src, dest, COPYFILE_EXCL);
console.log('File copied with safety flag');
}This pattern ensures the destination is clean before copying, so COPYFILE_EXCL won't conflict with existing files.
If you want different behavior when the file already exists, catch the EEXIST error and handle it appropriately:
const fs = require('fs').promises;
const { COPYFILE_EXCL } = require('fs').constants;
async function copyFileWithFallback(src, dest) {
try {
// Try to copy with safety flag
await fs.copyFile(src, dest, COPYFILE_EXCL);
console.log('File copied (destination did not exist)');
} catch (err) {
if (err.code === 'EEXIST') {
console.log('Destination already exists, using existing file');
// Could also skip, log a warning, or copy to alternate location
} else {
throw err; // Re-throw other errors like EACCES or EIO
}
}
}
// Or with synchronous code
const fs = require('fs');
try {
fs.copyFileSync(src, dest, fs.constants.COPYFILE_EXCL);
} catch (err) {
if (err.code === 'EEXIST') {
console.log('File already exists');
} else {
throw err;
}
}This approach is useful when you want to gracefully handle existing files without crashing.
If you need to preserve both the source and any existing destination, generate unique filenames:
const fs = require('fs').promises;
const path = require('path');
const { COPYFILE_EXCL } = require('fs').constants;
async function copyFileWithUniqueName(src, destDir) {
const baseName = path.basename(src);
const ext = path.extname(baseName);
const nameWithoutExt = path.basename(baseName, ext);
let destPath = path.join(destDir, baseName);
let counter = 1;
// Keep incrementing filename until we find an available name
while (true) {
try {
await fs.copyFile(src, destPath, COPYFILE_EXCL);
console.log(`Copied to: ${destPath}`);
return destPath;
} catch (err) {
if (err.code === 'EEXIST') {
// Try next numbered version
destPath = path.join(
destDir,
`${nameWithoutExt} (${counter})${ext}`
);
counter++;
} else {
throw err;
}
}
}
}
// Usage
await copyFileWithUniqueName('upload.txt', './uploads');
// Creates: uploads/upload.txt, uploads/upload (1).txt, uploads/upload (2).txt, etc.This pattern is common in file upload systems where multiple files might have the same name.
Use fs.access() or fs.stat() to check for the destination file before attempting to copy with COPYFILE_EXCL:
const fs = require('fs').promises;
const { COPYFILE_EXCL } = require('fs').constants;
async function copyFileIfNotExists(src, dest) {
try {
// Check if destination exists
await fs.access(dest);
console.log('Destination already exists, skipping copy');
return false;
} catch (err) {
if (err.code === 'ENOENT') {
// Destination doesn't exist, safe to copy
await fs.copyFile(src, dest, COPYFILE_EXCL);
console.log('File copied successfully');
return true;
} else {
throw err; // Other errors like EACCES
}
}
}
// Or using fs.existsSync for synchronous operations
function copyFileIfNotExistsSync(src, dest) {
if (fs.existsSync(dest)) {
console.log('Destination already exists, skipping');
return false;
}
fs.copyFileSync(src, dest, fs.constants.COPYFILE_EXCL);
console.log('File copied');
return true;
}Note: There's a small race condition between checking and copying. In concurrent scenarios, use error handling (step 3) instead.
For scripts that copy multiple files or run repeatedly, implement comprehensive error handling:
const fs = require('fs').promises;
const path = require('path');
const { COPYFILE_EXCL } = require('fs').constants;
async function robustFileCopy(src, dest, options = {}) {
const {
overwrite = false,
createUnique = false,
maxRetries = 3,
} = options;
// Strategy 1: Overwrite existing file
if (overwrite) {
try {
await fs.unlink(dest);
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
await fs.copyFile(src, dest);
return { success: true, path: dest };
}
// Strategy 2: Create unique filename if exists
if (createUnique) {
let finalDest = dest;
let counter = 0;
while (counter < 100) {
try {
await fs.copyFile(src, finalDest, COPYFILE_EXCL);
return { success: true, path: finalDest };
} catch (err) {
if (err.code === 'EEXIST') {
const ext = path.extname(dest);
const dir = path.dirname(dest);
const base = path.basename(dest, ext);
finalDest = path.join(dir, `${base}-${++counter}${ext}`);
} else {
throw err;
}
}
}
throw new Error('Could not find unique filename after 100 attempts');
}
// Strategy 3: Fail if exists (default with COPYFILE_EXCL)
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
await fs.copyFile(src, dest, COPYFILE_EXCL);
return { success: true, path: dest };
} catch (err) {
if (err.code === 'EEXIST' && attempt === maxRetries - 1) {
return { success: false, error: 'Destination already exists', code: 'EEXIST' };
}
if (err.code !== 'EEXIST') throw err;
// Retry with delay for transient issues
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
// Usage examples
await robustFileCopy('source.txt', 'dest.txt');
await robustFileCopy('source.txt', 'dest.txt', { overwrite: true });
await robustFileCopy('source.txt', 'dest.txt', { createUnique: true });This flexible approach handles different scenarios in a single function.
Understanding fs.constants.COPYFILE_EXCL: This flag makes fs.copyFile() fail rather than silently overwrite. It's equivalent to opening a file with the O_EXCL flag in C/C++. Use it when you absolutely need to prevent accidental file overwrites and want the operation to fail rather than silently lose data.
Performance considerations: Removing a file before copying (step 2) has filesystem overhead. If you're copying many files, consider the performance impact and whether overwriting is acceptable instead.
Concurrent file operations: In multi-threaded or worker-based scenarios, even checking and then copying creates a race condition. Always rely on the atomic error handling (step 3) rather than pre-checks.
Stream-based copying for large files: For very large files, fs.copyFile() uses efficient kernel-level copy-on-write on supported systems (Linux 4.5+, macOS). However, for more control over large file operations, consider using fs.createReadStream() and fs.createWriteStream().
File permissions and ownership: fs.copyFile() copies file content and mode bits but NOT ownership or extended attributes. The copied file will be owned by the process user. Use fs.chown() separately if you need to match ownership.
Windows and NTFS considerations: On Windows, fs.copyFile() may behave differently with special files (NTFS alternate streams). Test thoroughly on Windows if your application targets it.
Docker and containerized environments: When copying files between volumes or mounts, EEXIST errors may indicate permission issues or inconsistent filesystem state. Verify volume mounting and permissions before assuming the file actually exists.
Backup and disaster recovery: If building a backup system, use the unique naming strategy (step 4) to preserve previous versions. Never blindly overwrite backups.
Testing implications: Unit tests that copy files should clean up destination files in teardown hooks to avoid EEXIST errors on subsequent test runs. Use temporary directories for isolated test files.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams