{"record":{"id":"c7943b10036ab789","repo":"can1357/oh-my-pi","slug":"cannot-move-0-to-a-subdirectory-of-itself-1","errorCode":null,"errorMessage":"cannot move {0} to a subdirectory of itself, {1}","messagePattern":"cannot move (.+?) to a subdirectory of itself, (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/mv.rs","lineNumber":59,"sourceCode":"\tupdate_control::{self, UpdateMode},\n};\n\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)]","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/mv.rs#L41-L77","documentation":"This error comes from the `mv` builtin's MvError::SelfTargetSubdirectory variant in crates/pi-builtins/src/mv.rs. It is raised by `assert_not_same_file` when the canonicalized destination path is equal to, or nested inside, the canonicalized source path — i.e. moving a directory (or file) into itself. Such a move is impossible without corrupting the filesystem layout, so the builtin refuses it. The message mirrors GNU coreutils mv's wording, showing both the source operand and the computed effective target.","triggerScenarios":"Calling `mv dir dir/sub` (destination is inside the source); `mv dir dir` (same path); `mv .. ..` or a path ending in `/.` that resolves to an ancestor of the target; hardlinks or one-way symlinks pointing at the same file; it is raised in `assert_not_same_file` (mv.rs:728-735) when `canonicalized_target.starts_with(&canonicalized_source)` and the source is not a symlink-to-directory.","commonSituations":"Script variables where both paths come from unexpanded variables that happen to hold the same directory; recursive copy/move scripts that compute destinations from the source path (e.g. `mv $dir $dir/backup`); shell globs accidentally matching the source directory as its own destination; moving a mount point into a subdirectory of itself.","solutions":["Compute the destination outside the source tree, e.g. move the directory to a sibling or parent directory instead of a path under itself.","Canonicalize both paths before the move (e.g. `realpath`) and add a script guard: skip when TARGET starts with SOURCE/.","If the intent was to restructure contents, move the inner items out first, then relocate the outer directory.","If a symlink-to-directory is involved and the error is unexpected, verify the link target with `readlink` — the check intentionally allows symlink-into-self but not real-directory-into-self."],"exampleFix":"// before (moves mydir into itself, fails)\nmv \"$dir\" \"$dir/backup\"\n\n// after (move to a sibling location)\nmv \"$dir\" \"${dir%/*}/backup-$dir\"","handlingStrategy":"validation","validationCode":"import * as path from \"node:path\";\nimport * as fs from \"node:fs/promises\";\n\nexport async function canMoveInto(src: string, dest: string): Promise<boolean> {\n  const [srcAbs, destAbs] = [path.resolve(src), path.resolve(dest)];\n  if (srcAbs === destAbs) return false;\n  // resolve symlinks so a link to src doesn't fool the check\n  const srcReal = await fs.realpath(srcAbs).catch(() => srcAbs);\n  const destReal = await fs.realpath(destAbs).catch(() => destAbs);\n  return !destReal.startsWith(srcReal + path.sep);\n}\n// skip or rewrite the destination when !await canMoveInto(src, dest)","typeGuard":null,"tryCatchPattern":"try {\n  await runBuiltin(\"mv\", [src, dest]);\n} catch (e) {\n  if (String(e).includes(\"to a subdirectory of itself\")) {\n    logger.warn(\"skipping self-referential move\", { src, dest });\n  } else throw e;\n}","preventionTips":["Always compute destinations outside the source subtree before calling mv.","Canonicalize (realpath) variable-supplied paths before comparing or moving.","In recursive scripts, assert dest does not start with src + separator.","Prefer moving to sibling/parent paths rather than `${src}/...`-derived names."],"tags":["mv","filesystem","invalid-destination","path-resolution"],"backgroundTag":"moving-directory-into-itself","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}