can1357/oh-my-pi · error

cannot stat {0}: Not a directory

Error message

cannot stat {0}: Not a directory

What it means

GNU-compatible `mv` error (`MvError::CannotStatNotADirectory`, crates/pi-builtins/src/mv.rs:55). Same underlying condition as `NoSuchFile` — `symlink_metadata` failed on the source — but selected when the source operand ends with a trailing path separator (`path_ends_with_terminator`). The trailing `/` asserts the operand must be a directory, and stat on `foo/` fails with ENOTDIR rather than ENOENT on most platforms.

Source

Thrown at crates/pi-builtins/src/mv.rs:55

	fs::{
		MissingHandling, ResolveMode, are_hardlinks_or_one_way_symlink_to_same_file,
		are_hardlinks_to_same_file, canonicalize, path_ends_with_terminator,
	},
	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)]

View on GitHub (pinned to 9690622007)

Solutions

  1. Check whether the path exists: `ls -ld foo` — if missing, create it or fix the variable that supplies it.
  2. If the operand is actually a regular file, drop the trailing slash: `mv foo bar` instead of `mv foo/ bar`.
  3. Guard directory operands: `[ -d "$src" ] && mv "$src/" "$dst"`.
  4. Strip accidental trailing separators in scripts: `src="${src%/}"`.

Example fix

# before
mv "$build_dir/" out/   # build_dir unset => mv / out/  -> cannot stat '/': Not a directory
# after
: "${build_dir:?build_dir not set}"
[ -d "$build_dir" ] && mv "$build_dir" out/
Defensive patterns

Strategy: validation

Validate before calling

src="${src%/}"            # drop trailing separator
[ -d "$src" ] || { echo "mv: '$src' is not a directory" >&2; exit 1; }

Try / catch

if ! mv "$src" "$dst" 2>err.txt; then cat err.txt >&2; grep -q 'Not a directory' err.txt && exit 1; fi

Prevention

When it happens

Trigger: `mv foo/ bar` where `foo` does not exist (or is a regular file, so `foo/` cannot name a directory) — the trailing slash triggers this variant in `handle_two_paths`.

Common situations: Tab-completed or copy-pasted paths that keep a trailing `/` after the target was renamed or deleted; scripts appending `/` to a directory variable that is empty or points at a file; shell scripts written expecting `foo/` to still resolve when `foo` is a symlink later removed.

Related errors


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