modelcontextprotocol/servers · error · Error
Destination already exists: ${destinationPath}
Error message
Destination already exists: ${destinationPath} What it means
moveFile performs a non-destructive move: it first lstat's the destination and, if anything (file, directory, or symlink) already exists there, throws instead of letting fs.rename silently overwrite it. This exists to prevent data loss, since rename(2) would otherwise replace the target atomically.
Source
Thrown at src/filesystem/lib.ts:255
}
export async function moveFile(sourcePath: string, destinationPath: string): Promise<void> {
// The move_file tool contract (and README) state the operation fails if the
// destination already exists. fs.rename would silently overwrite it, which is
// a data-loss bug, so reject up front when anything - file, directory, or
// symlink - occupies the target. lstat is used so an existing symlink at the
// destination is detected rather than followed.
try {
await fs.lstat(destinationPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
await fs.rename(sourcePath, destinationPath);
return;
}
throw error;
}
throw new Error(`Destination already exists: ${destinationPath}`);
}
// File Editing Functions
interface FileEdit {
oldText: string;
newText: string;
}
export async function applyFileEdits(
filePath: string,
edits: FileEdit[],
dryRun: boolean = false
): Promise<string> {
// Read file content and normalize line endings
const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8'));
// Apply edits sequentiallyView on GitHub (pinned to 579c3903f3)
Solutions
- Delete or rename the existing destination first, then retry the move
- Choose a unique destination name (e.g. append a timestamp or version suffix)
- If overwrite is genuinely intended, delete the destination explicitly via an allowed tool rather than expecting move_file to clobber it
Example fix
// before
await move_file('/allowed/a.txt', '/allowed/b.txt'); // b.txt exists -> error
// after
await move_file('/allowed/b.txt', '/allowed/b.txt.bak'); // or unlink first
await move_file('/allowed/a.txt', '/allowed/b.txt'); Defensive patterns
Strategy: try-catch
Validate before calling
try { await fs.lstat(dest); throw new Error(`Destination exists: ${dest}`); } catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e; } Type guard
async function destinationIsFree(dest: string): Promise<boolean> {
try { await fs.lstat(dest); return false; } catch (e) { return (e as NodeJS.ErrnoException).code === 'ENOENT'; }
} Try / catch
try {
await move_file(src, dest);
} catch (err) {
if (err.message.startsWith('Destination already exists')) {
// pick a unique name or archive the existing destination, then retry
} else throw err;
} Prevention
- Check destination availability with lstat before issuing the move
- Generate unique destination names (timestamps, counters) in scripts
- Make move scripts idempotent: skip or archive when destination exists
- Clean up partial-run artifacts that occupy destination names
When it happens
Trigger: Calling move_file(sourcePath, destinationPath) via the move_file tool where destinationPath exists on disk — including when it is only a dangling symlink (lstat detects it without following).
Common situations: Re-running a script that moves files without cleaning up; moving into a directory that already contains same-named files; race where another process created the destination between planning and the move.
Related errors
- Parent directory does not exist: ${parentDir}
- Could not find exact match for edit:\n${edit.oldText}
- Ambiguous Unicode path component: ${requestedPart}
- Access denied - symlink target outside allowed directories:
- Access denied - Windows-style path received on a POSIX host:
AI-assisted analysis of modelcontextprotocol/servers@579c3903f3 (2026-09-01).
Data as JSON: /api/errors/0ab28626daeeac27.
Report an issue: GitHub.