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

backing up {} might destroy source; {} not moved

Error message

backing up {} might destroy source;  {} not moved

What it means

Raised by handle_two_paths when `mv --backup=numbered/simple` (BackupMode::Simple) would overwrite the backup copy of the source with the source itself. If the target path is exactly what the source's backup file would be named (source + backup suffix), performing the move-and-back-up would first back the target up over the source, destroying it. The library refuses with `backing up 'X' might destroy source; 'Y' not moved` (note the deliberate double space, matching GNU coreutils) and moves nothing.

Source

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

	}
}

fn parse_paths(files: &[OsString], opts: &Options) -> Vec<PathBuf> {
	let paths = files.iter().map(Path::new);

	if opts.strip_slashes {
		paths
			.map(|p| p.components().as_path().to_owned())
			.collect::<Vec<PathBuf>>()
	} else {
		paths.map(ToOwned::to_owned).collect::<Vec<PathBuf>>()
	}
}

fn handle_two_paths(host: &mut Host, source: &Path, target: &Path, opts: &Options) -> MvResult<()> {
	if opts.backup == BackupMode::Simple && source_is_target_backup(source, target, &opts.suffix) {
		return Err(
			io::Error::new(
				io::ErrorKind::NotFound,
				format!(
					"backing up {} might destroy source;  {} not moved",
					target.quote(),
					source.quote()
				),
			)
			.into(),
		);
	}

	let source_fs = host.resolve(source);
	let target_fs = host.resolve(target);

	if source_fs.symlink_metadata().is_err() {
		return Err(if path_ends_with_terminator(source) {
			MvError::CannotStatNotADirectory(source.quote().to_string()).into()
		} else {

View on GitHub (pinned to 9690622007)

Solutions

  1. Rename the source to a target that is not source+suffix, or use a different --suffix value
  2. Drop the --backup / --b=simple flag for this invocation since you do not need a backup when the name is already backup-shaped
  3. Check the command's arguments for glob expansion pairing a file with its backup twin (ls to confirm both exist)
  4. Use numbered backups (--backup=numbered) if you genuinely need both copies preserved

Example fix

// before
mv --b=simple config.yaml config.yaml~
// error: backing up 'config.yaml~' might destroy source;  'config.yaml' not moved
// after
mv --b=simple config.yaml config.yaml.bak
// or: mv config.yaml config.yaml~  (no --backup flag)
Defensive patterns

Strategy: validation

Validate before calling

// refuse the dangerous pairing before invoking mv
const suffix = "~";
if (useSimpleBackup && dest === src + suffix) {
  throw new Error(`refusing: backing up '${dest}' might destroy source '${src}'`);
}

Try / catch

try {
  await run("mv", ["--b=simple", src, dest]);
} catch (err) {
  if (String(err.stderr).includes("might destroy source")) {
    // retry without backup mode or with a different suffix
    await run("mv", [src, dest]);
  } else throw err;
}

Prevention

When it happens

Trigger: Running `mv --b=simple foo foo~` (with default suffix `~`), i.e. mv foo foo~, or any invocation where source_is_target_backup(source, target, suffix) is true: the target equals source plus the configured --suffix, while simple backup mode is active.

Common situations: Shell globs or loops that accidentally pair a file with its own backup name (`mv $f $f~` in a cleanup loop); scripted editor-style backups where suffix logic collides with the actual filename; users accustomed to `cp --backup` trying the same pattern with mv.

Related errors


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