can1357/oh-my-pi · error

cannot overwrite non-directory {1} with directory {0}

Error message

cannot overwrite non-directory {1} with directory {0}

What it means

This is MvError::NonDirectoryToDirectory in crates/pi-builtins/src/mv.rs. Raised when the source is a directory (or non-file operand) and the destination already exists as a non-directory file — mv cannot replace a regular file with a directory. In `handle_two_paths` (mv.rs:626-641) this fires when `target_fs.exists() && source_is_dir` and the destination is not a directory, after the overwrite-mode prompt (declining an `-i` prompt also surfaces here). Format arguments: {0} is the source, {1} the destination.

Source

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

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

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove or rename the existing file first (`rm dest` or `mv dest dest.bak`) if the directory should replace it, then re-run mv.
  2. Verify operand order and target type: `file dest` / `ls -ld` to confirm the last argument is a directory.
  3. If the destination was meant to be a target directory, create it (`mkdir -p dest`) and move into it.
  4. Check for a symlink at the destination with `readlink dest` and correct or remove it.

Example fix

// before (fails: dest is a regular file)
mv srcdir dest

// after
dest_is_file && { mv dest dest.old; }
mv srcdir dest   # or: mkdir -p dest && mv srcdir dest/srcdir
Defensive patterns

Strategy: validation

Validate before calling

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

export async function moveDirIntoOrReplace(src: string, dest: string): Promise<void> {
  const srcIsDir = (await fs.stat(src)).isDirectory();
  const destSt = await fs.lstat(dest).catch(() => null);
  if (srcIsDir && destSt && !destSt.isDirectory()) {
    // decide explicitly: remove, back up, or pick a new destination
    await fs.rename(dest, `${dest}.old`);
  }
  // proceed with mv
}

Try / catch

try {
  await runBuiltin("mv", [src, dest]);
} catch (e) {
  if (String(e).includes("cannot overwrite non-directory")) {
    logger.error("destination exists as a non-directory", { src, dest });
  } else throw e;
}

Prevention

When it happens

Trigger: `mv srcdir existing_file` where existing_file is a regular file or symlink-to-file; `mv -T srcdir existing_file`; multiple sources with a file as the final target directory operand; answering 'n' to an interactive overwrite prompt in this same branch.

Common situations: A path that used to be a directory was replaced by a symlink or file (stale checkout, tooling change); typos where the last argument is a file instead of the intended target directory; scripts that expected `dest/` to exist and got a file of the same name; package managers leaving a file where a directory is expected.

Related errors


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