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

  1. Compute the destination outside the source tree, e.g. move the directory to a sibling or parent directory instead of a path under itself.
  2. Canonicalize both paths before the move (e.g. `realpath`) and add a script guard: skip when TARGET starts with SOURCE/.
  3. If the intent was to restructure contents, move the inner items out first, then relocate the outer directory.
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c7943b10036ab789. Report an issue: GitHub.