{"record":{"id":"2e43e60b10c79c56","repo":"can1357/oh-my-pi","slug":"inter-device-move-failed-to-unable-to-remo","errorCode":null,"errorMessage":"inter-device move failed: {} to {}; unable to remove target: {err}","messagePattern":"inter-device move failed: (.+?) to (.+?); unable to remove target: (.+?)","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/mv.rs","lineNumber":1398,"sourceCode":"}\n\nfn rename_file_fallback(\n\thost: &mut Host,\n\tfrom: &Path,\n\tto: &Path,\n\t#[cfg(unix)] hardlink_tracker: Option<&mut HardlinkTracker>,\n\t#[cfg(unix)] hardlink_scanner: Option<&HardlinkGroupScanner>,\n) -> io::Result<()> {\n\tlet to_fs = host.resolve(to);\n\t// Remove existing target file if it exists\n\tif to_fs.is_symlink() {\n\t\tfs::remove_file(&to_fs).map_err(|err| {\n\t\t\tlet inter_device_msg = format!(\n\t\t\t\t\"inter-device move failed: {} to {}; unable to remove target: {err}\",\n\t\t\t\tfrom.quote(),\n\t\t\t\tto.quote()\n\t\t\t);\n\t\t\tio::Error::new(err.kind(), inter_device_msg)\n\t\t})?;\n\t} else if to_fs.exists() {\n\t\t// For non-symlinks, just remove the file without special error handling\n\t\tfs::remove_file(&to_fs)?;\n\t}\n\n\t// Check if this file is part of a hardlink group and if so, create a hardlink\n\t// instead of copying\n\t#[cfg(unix)]\n\t{\n\t\tif let (Some(tracker), Some(scanner)) = (hardlink_tracker, hardlink_scanner) {\n\t\t\tuse hardlink::HardlinkOptions;\n\t\t\tlet hardlink_options = HardlinkOptions::default();\n\t\t\tif let Some(existing_target) = tracker.check_hardlink(host, from, to, scanner, &hardlink_options)\n\t\t\t{\n\t\t\t\t// Create a hardlink to the first moved file instead of copying\n\t\t\t\tfs::hard_link(host.resolve(&existing_target), &to_fs)?;\n\t\t\t\tfs::remove_file(host.resolve(from))?;","sourceCodeStart":1380,"sourceCodeEnd":1416,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/mv.rs#L1380-L1416","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the underlying removal error shown after 'unable to remove target:' (check permissions/ownership of the destination directory, free space, and read-only status)","Delete or rename the existing symlink at the destination manually before the move","Check that the destination filesystem is mounted read-write (mount | grep <dest>) and remount if needed","Move to a destination path that does not already contain a symlink"],"exampleFix":"# before\nmv /home/user/data.txt /mnt/usb/data.txt\n# error: inter-device move failed: '/home/user/data.txt' to '/mnt/usb/data.txt'; unable to remove target: Permission denied\n# after\nsudo chown $(whoami) /mnt/usb/   # or: rm /mnt/usb/data.txt\nmv /home/user/data.txt /mnt/usb/data.txt","handlingStrategy":"try-catch","validationCode":"const { lstatSync, accessSync, constants, statSync } = require(\"node:fs\");\nfunction canMoveOntoDest(dest) {\n  const dir = path.dirname(dest);\n  try { accessSync(dir, constants.W_OK); } catch { return false; }\n  const st = lstatSync(dest, { throwIfNoEntry: false });\n  if (st && st.isSymbolicLink()) {\n    try { return statSync(dest).isFile() || true; } catch { /* dangling link: removable if dir is writable */ }\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await run(\"mv\", [src, dest]);\n} catch (err) {\n  if (String(err.stderr).includes(\"inter-device move failed\")) {\n    const cause = String(err.stderr).split(\"unable to remove target:\")[1]?.trim();\n    if (cause?.includes(\"Permission denied\")) {\n      // fix perms or remove the target symlink, then retry once\n      await fs.promises.unlink(dest).catch(() => {});\n      await run(\"mv\", [src, dest]);\n    } else throw err;\n  } else throw err;\n}","preventionTips":["Verify the destination directory is writable by the current user before cross-device moves","Remount read-only destinations read-write before scripted moves","Clear stale symlinks from destination directories ahead of bulk moves","Check free space and mount status (ro/rw) of the target volume when moving across devices"],"tags":["filesystem","mv","permissions","cross-device"],"backgroundTag":"inter-device-move-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}