can1357/oh-my-pi · error
cannot move {0} to a subdirectory of itself, {1}
Error message
cannot move {0} to a subdirectory of itself, {1} What it means
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.
Source
Thrown at crates/pi-builtins/src/mv.rs:59
update_control::{self, UpdateMode},
};
use crate::host::{Host, Utility, format_usage, matches_parser, util};
#[cfg(unix)]
use self::hardlink::{
HardlinkGroupScanner, HardlinkOptions, HardlinkTracker, create_hardlink_context,
with_optional_hardlink_context,
};
#[derive(Debug, Error)]
enum MvError {
#[error("cannot stat {0}: No such file or directory")]
NoSuchFile(String),
#[error("cannot stat {0}: Not a directory")]
CannotStatNotADirectory(String),
#[error("{0} and {1} are the same file")]
SameFile(String, String),
#[error("cannot move {0} to a subdirectory of itself, {1}")]
SelfTargetSubdirectory(String, String),
#[error("cannot overwrite directory {0} with non-directory")]
DirectoryToNonDirectory(String),
#[error("cannot overwrite non-directory {1} with directory {0}")]
NonDirectoryToDirectory(String, String),
#[error("target {0}: Not a directory")]
NotADirectory(String),
#[error("target directory {0}: Not a directory")]
TargetNotADirectory(String),
#[error("failed to access {0}: Not a directory")]
FailedToAccessNotADirectory(String),
}
#[derive(Debug, Error)]
enum MvFailure {
#[error(transparent)]
Move(#[from] MvError),
#[error(transparent)]View on GitHub (pinned to 9690622007)
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.
Example fix
// before (moves mydir into itself, fails)
mv "$dir" "$dir/backup"
// after (move to a sibling location)
mv "$dir" "${dir%/*}/backup-$dir" Defensive patterns
Strategy: validation
Validate before calling
import * as path from "node:path";
import * as fs from "node:fs/promises";
export async function canMoveInto(src: string, dest: string): Promise<boolean> {
const [srcAbs, destAbs] = [path.resolve(src), path.resolve(dest)];
if (srcAbs === destAbs) return false;
// resolve symlinks so a link to src doesn't fool the check
const srcReal = await fs.realpath(srcAbs).catch(() => srcAbs);
const destReal = await fs.realpath(destAbs).catch(() => destAbs);
return !destReal.startsWith(srcReal + path.sep);
}
// skip or rewrite the destination when !await canMoveInto(src, dest) Try / catch
try {
await runBuiltin("mv", [src, dest]);
} catch (e) {
if (String(e).includes("to a subdirectory of itself")) {
logger.warn("skipping self-referential move", { src, dest });
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cannot stat {0}: No such file or directory
- cannot stat {0}: Not a directory
- {0} and {1} are the same file
- cannot overwrite directory {0} with non-directory
- cannot overwrite non-directory {1} with directory {0}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/c7943b10036ab789.
Report an issue: GitHub.