{"record":{"id":"b038e8dcac41eb40","repo":"can1357/oh-my-pi","slug":"cannot-overwrite-directory-0-with-non-directory","errorCode":null,"errorMessage":"cannot overwrite directory {0} with non-directory","messagePattern":"cannot overwrite directory (.+?) with non-directory","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/mv.rs","lineNumber":61,"sourceCode":"\nuse crate::host::{Host, Utility, format_usage, matches_parser, util};\n#[cfg(unix)]\nuse self::hardlink::{\n\tHardlinkGroupScanner, HardlinkOptions, HardlinkTracker, create_hardlink_context,\n\twith_optional_hardlink_context,\n};\n\n#[derive(Debug, Error)]\nenum MvError {\n\t#[error(\"cannot stat {0}: No such file or directory\")]\n\tNoSuchFile(String),\n\t#[error(\"cannot stat {0}: Not a directory\")]\n\tCannotStatNotADirectory(String),\n\t#[error(\"{0} and {1} are the same file\")]\n\tSameFile(String, String),\n\t#[error(\"cannot move {0} to a subdirectory of itself, {1}\")]\n\tSelfTargetSubdirectory(String, String),\n\t#[error(\"cannot overwrite directory {0} with non-directory\")]\n\tDirectoryToNonDirectory(String),\n\t#[error(\"cannot overwrite non-directory {1} with directory {0}\")]\n\tNonDirectoryToDirectory(String, String),\n\t#[error(\"target {0}: Not a directory\")]\n\tNotADirectory(String),\n\t#[error(\"target directory {0}: Not a directory\")]\n\tTargetNotADirectory(String),\n\t#[error(\"failed to access {0}: Not a directory\")]\n\tFailedToAccessNotADirectory(String),\n}\n\n#[derive(Debug, Error)]\nenum MvFailure {\n\t#[error(transparent)]\n\tMove(#[from] MvError),\n\t#[error(transparent)]\n\tIo(#[from] io::Error),\n\t#[error(\"{0}\")]","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/mv.rs#L43-L79","documentation":"This is MvError::DirectoryToNonDirectory in crates/pi-builtins/src/mv.rs. It is raised when the destination path already exists and is a directory, but the source is a non-directory file — so mv would have to replace a directory with a file, which the kernel rename cannot do and coreutils forbids. Triggered from `handle_two_paths` (mv.rs:609-621) when `-T/--no-target-directory` is used with a directory target, or via the equivalent branch when target exists as a dir. Note the payload is the target path, matching GNU mv's message.","triggerScenarios":"`mv -T somefile existing_dir` (file over an existing directory); `mv --no-target-directory file dir_that_exists`; mv invoked with a symlink that resolves to a directory as the second operand while the source is a plain file and -T is set.","commonSituations":"Scripts that assumed the destination was a regular file but a directory of that name was created earlier (e.g. by `mkdir -p` in setup); users typing `mv -T newfile conf` where `conf/` is a config directory; build tooling that switched between file and directory outputs across versions, leaving a stale directory in place.","solutions":["Remove or rename the existing directory first if it is truly meant to be replaced, then re-run the move.","Drop the `-T` flag if the intent was to move the source INTO the directory (`mv file dir/`).","Check the destination with `ls -ld dest` to confirm its type before scripting the move.","If the directory should hold the file, move to `dest/basename` instead of overwriting `dest` itself."],"exampleFix":"// before (fails: conf is a directory)\nmv -T config.yaml conf\n\n// after: move the file into the directory\nmv config.yaml conf/config.yaml\n// or, to truly replace the directory:\nrm -rf conf && mv config.yaml conf","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs/promises\";\n\nexport async function safeMoveFileOver(src: string, dest: string): Promise<void> {\n  const st = await fs.lstat(dest).catch(() => null);\n  if (st?.isDirectory()) {\n    throw new Error(`destination ${dest} is a directory; move into it or remove it first`);\n  }\n  // proceed with mv src dest (-T semantics)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runBuiltin(\"mv\", [\"-T\", src, dest]);\n} catch (e) {\n  if (String(e).includes(\"cannot overwrite directory\")) {\n    logger.error(\"destination is an existing directory\", { dest });\n  } else throw e;\n}","preventionTips":["Stat the destination and branch on isDirectory() before every scripted move.","Avoid -T unless you are certain the destination is a regular file.","Don't mkdir -p a path you later intend to overwrite with a file.","Clean stale directories left by earlier build/setup steps before moving files onto them."],"tags":["mv","filesystem","type-mismatch","overwrite"],"backgroundTag":"mv-directory-over-file","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}