can1357/oh-my-pi · error · ToolError
rename target already exists: ${formatPathRelativeToCwd(newP
Error message
rename target already exists: ${formatPathRelativeToCwd(newPath, cwd)} What it means
When applying a WorkspaceEdit containing a RenameFile operation, the destination already exists on disk. Without options.overwrite the library refuses to silently destroy the existing file; without ignoreIfExists it also won't skip, so it throws to keep the workspace consistent (the surrounding code restores any displaced files on failure).
Source
Thrown at packages/coding-agent/src/lsp/edits.ts:371
continue;
}
applied.push(`Created ${formatPathRelativeToCwd(filePath, cwd)}`);
record({ kind: "create", uri: op.uri });
} else if (op.kind === "rename") {
const oldPath = uriToFile(op.oldUri);
const newPath = uriToFile(op.newUri);
await fs.mkdir(path.dirname(newPath), { recursive: true });
if (oldPath !== newPath) {
// Displace an overwritten destination into a kernel-reserved sibling
// temp dir (same filesystem, so the moves stay atomic) instead of
// deleting it, so a failed rename (EXDEV, permissions) can restore
// it and leave the workspace exactly as it was.
let displaced: { dir: string; file: string } | undefined;
try {
const targetStat = await fs.lstat(newPath);
if (!op.options?.overwrite) {
if (op.options?.ignoreIfExists) continue;
throw new ToolError(`rename target already exists: ${formatPathRelativeToCwd(newPath, cwd)}`);
}
// Only displace the destination when it is a distinct file. On a
// case-insensitive filesystem a case-only rename resolves both
// paths to the same inode; moving newPath aside would move the
// source, so let fs.rename change the case in place instead.
const sourceStat = await fs.lstat(oldPath);
if (sourceStat.dev !== targetStat.dev || sourceStat.ino !== targetStat.ino) {
const holdDir = await fs.mkdtemp(path.join(path.dirname(newPath), ".omp-displaced-"));
const holdFile = path.join(holdDir, path.basename(newPath));
try {
await fs.rename(newPath, holdFile);
} catch (error) {
await fs.rm(holdDir, { recursive: true, force: true }).catch(() => {});
throw error;
}
displaced = { dir: holdDir, file: holdFile };
}
} catch (error) {View on GitHub (pinned to 9690622007)
Solutions
- Check whether the target path should exist; delete or move it if the rename is intended to replace it
- If replacement is intended, ensure the server sets overwrite:true in the RenameFile options
- If skipping is intended, ensure the server sets ignoreIfExists:true
Example fix
// before
{ kind: "rename", oldUri, newUri }
// after (intentional replace)
{ kind: "rename", oldUri, newUri, options: { overwrite: true } } Defensive patterns
Strategy: validation
Validate before calling
import * as fs from 'node:fs/promises';
let exists = false;
try { await fs.lstat(newPath); exists = true; } catch {}
if (exists && !op.options?.overwrite && !op.options?.ignoreIfExists) {
throw new Error(`target exists: ${newPath}`);
} Try / catch
try {
await applyWorkspaceEdit(edit, cwd);
} catch (err) {
if (err.message.startsWith('rename target already exists:')) {
// decide: prompt user, or re-issue edit with options: { overwrite: true }
}
throw err;
} Prevention
- Check the destination path before issuing renames to existing files
- Ensure servers set overwrite or ignoreIfExists on RenameFile operations when appropriate
- Be careful with case-only renames on case-insensitive filesystems
When it happens
Trigger: A server-issued rename (e.g. file rename refactor) targets a path that already exists and the RenameFile options carry neither overwrite:true nor ignoreIfExists:true.
Common situations: Renaming a file to a name that already exists in the same directory; case-insensitive filesystems where target differs only in case; re-running an edit after a partial application.
Related errors
- File not found: ${filePath}
- unknown filetype: {ft_debug}
- Is a directory
- Too many levels of symbolic links
- {}: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8147d7ecdab45fff.
Report an issue: GitHub.