can1357/oh-my-pi · error · io::Error

inter-device move failed: {} to {}; unable to remove target:

Error message

inter-device move failed: {} to {}; unable to remove target: {err}

What it means

Raised in rename_file_fallback during an inter-device (cross-filesystem) move when the fallback first tries to remove an existing target symlink at the destination and that removal fails. The error wraps the original removal error with `inter-device move failed: 'from' to 'to'; unable to remove target: <err>`, preserving the original io::ErrorKind. It signals that the copy-based fallback could not even clear the destination, so the move is aborted.

Source

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

}

fn rename_file_fallback(
	host: &mut Host,
	from: &Path,
	to: &Path,
	#[cfg(unix)] hardlink_tracker: Option<&mut HardlinkTracker>,
	#[cfg(unix)] hardlink_scanner: Option<&HardlinkGroupScanner>,
) -> io::Result<()> {
	let to_fs = host.resolve(to);
	// Remove existing target file if it exists
	if to_fs.is_symlink() {
		fs::remove_file(&to_fs).map_err(|err| {
			let inter_device_msg = format!(
				"inter-device move failed: {} to {}; unable to remove target: {err}",
				from.quote(),
				to.quote()
			);
			io::Error::new(err.kind(), inter_device_msg)
		})?;
	} else if to_fs.exists() {
		// For non-symlinks, just remove the file without special error handling
		fs::remove_file(&to_fs)?;
	}

	// Check if this file is part of a hardlink group and if so, create a hardlink
	// instead of copying
	#[cfg(unix)]
	{
		if let (Some(tracker), Some(scanner)) = (hardlink_tracker, hardlink_scanner) {
			use hardlink::HardlinkOptions;
			let hardlink_options = HardlinkOptions::default();
			if let Some(existing_target) = tracker.check_hardlink(host, from, to, scanner, &hardlink_options)
			{
				// Create a hardlink to the first moved file instead of copying
				fs::hard_link(host.resolve(&existing_target), &to_fs)?;
				fs::remove_file(host.resolve(from))?;

View on GitHub (pinned to 9690622007)

Solutions

  1. Fix the underlying removal error shown after 'unable to remove target:' (check permissions/ownership of the destination directory, free space, and read-only status)
  2. Delete or rename the existing symlink at the destination manually before the move
  3. Check that the destination filesystem is mounted read-write (mount | grep <dest>) and remount if needed
  4. Move to a destination path that does not already contain a symlink

Example fix

# before
mv /home/user/data.txt /mnt/usb/data.txt
# error: inter-device move failed: '/home/user/data.txt' to '/mnt/usb/data.txt'; unable to remove target: Permission denied
# after
sudo chown $(whoami) /mnt/usb/   # or: rm /mnt/usb/data.txt
mv /home/user/data.txt /mnt/usb/data.txt
Defensive patterns

Strategy: try-catch

Validate before calling

const { lstatSync, accessSync, constants, statSync } = require("node:fs");
function canMoveOntoDest(dest) {
  const dir = path.dirname(dest);
  try { accessSync(dir, constants.W_OK); } catch { return false; }
  const st = lstatSync(dest, { throwIfNoEntry: false });
  if (st && st.isSymbolicLink()) {
    try { return statSync(dest).isFile() || true; } catch { /* dangling link: removable if dir is writable */ }
  }
  return true;
}

Try / catch

try {
  await run("mv", [src, dest]);
} catch (err) {
  if (String(err.stderr).includes("inter-device move failed")) {
    const cause = String(err.stderr).split("unable to remove target:")[1]?.trim();
    if (cause?.includes("Permission denied")) {
      // fix perms or remove the target symlink, then retry once
      await fs.promises.unlink(dest).catch(() => {});
      await run("mv", [src, dest]);
    } else throw err;
  } else throw err;
}

Prevention

When it happens

Trigger: Moving a file across devices (e.g. /home to /mnt/usb) where the destination path is an existing symlink whose fs::remove_file fails - typically due to permissions on the containing directory, a read-only filesystem, or the link being in use; the map_err at mv.rs:1392-1399 attaches the inter-device context.

Common situations: Cross-drive moves onto read-only or full USB/network mounts; destination directory lacking write permission so the existing symlink cannot be unlinked; stale symlinks in target directories owned by another user; Docker/volume mounts where unlink is restricted.

Related errors


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