can1357/oh-my-pi · error

target {0}: Not a directory

Error message

target {0}: Not a directory

What it means

This is MvError::NotADirectory from crates/pi-builtins/src/mv.rs. Raised in `move_files_into_dir` (mv.rs:785-787) when the multi-operand form of mv resolves its target directory and finds it is not a directory: `mv src... target_dir` or `mv -t DIR src...` with a non-directory final operand. In GNU mv, a non-existent destination directory is simply created only for a single rename; for the many-to-one form the last operand must already be a directory, so the builtin reports this error instead.

Source

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

	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)]
	Io(#[from] io::Error),
	#[error("{0}")]
	Message(String),
}

type MvResult<T> = Result<T, MvFailure>;

View on GitHub (pinned to 9690622007)

Solutions

  1. Create the target directory before the move: `mkdir -p dest`.
  2. Verify the last operand is the intended directory (`ls -ld dest`) and fix typos.
  3. If you actually want a rename (single source to a new name), use the two-operand form `mv src dest` rather than many-to-one.
  4. Resolve any symlink standing in for the target and point the command at the real directory.

Example fix

// before (fails: backups is not a directory)
mv file1 file2 backups

// after
mkdir -p backups && mv file1 file2 backups/
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs/promises";

export async function ensureTargetDir(dir: string): Promise<string> {
  const st = await fs.stat(dir).catch(() => null);
  if (!st?.isDirectory()) {
    await fs.mkdir(dir, { recursive: true });
  }
  return dir;
}
// await ensureTargetDir(dest); then mv src... dest

Try / catch

try {
  await runBuiltin("mv", [...sources, dest]);
} catch (e) {
  if (String(e).startsWith("target ") && String(e).includes("Not a directory")) {
    await fs.mkdir(dest, { recursive: true });
    await runBuiltin("mv", [...sources, dest]); // retry once
  } else throw e;
}

Prevention

When it happens

Trigger: `mv a b c dest` where dest does not exist or is a regular file; `mv -t somefile src1 src2` where somefile is not a directory; `mv -t nonexistent src` (target directory never created); a symlink as target whose link target is a file.

Common situations: Batch-move scripts where the destination directory was never `mkdir -p`'d; typos in the directory name so it resolves to nothing; build scripts expecting a directory that a previous step failed to create; glob expansion producing a file as the final argument.

Related errors


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