can1357/oh-my-pi · error · Error

MV destination is the same as ${target.path}.

Error message

MV destination is the same as ${target.path}.

What it means

HashlinePatcher.prepare validates the target file before applying a patch. When the operation is a move (fileOp.kind === 'move') and the canonicalized destination equals the canonicalized source path, it throws, since moving a file onto itself is a no-op that would corrupt the patch pipeline's assumptions.

Source

Thrown at packages/hashline/src/patcher.ts:389

					pathRecoveredFromTagMessage(target.path, recovered.section.path, target.fileHash as string),
				);
				target = recovered.section;
				canonicalPath = recovered.canonicalPath;
				read = await this.#tryRead(target.path);
			}
		}

		// Gate the final (possibly recovered) target before any write work, so
		// an unrecoverable read-only target (e.g. a plan-mode working-tree path)
		// fails with the write guard rather than a misleading "file not found".
		await this.fs.preflightWrite(target.path, { fileOp });

		if (!read.exists) {
			throw new Error(`File not found: ${target.path}. Use the write tool to create new files.`);
		}

		if (fileOp?.kind === "move" && this.fs.canonicalPath(fileOp.dest) === canonicalPath) {
			throw new Error(`MV destination is the same as ${target.path}.`);
		}

		const { bom: bomFromText, text } = stripBom(read.rawContent);
		const bom = bomFromText || (await this.#readBinaryBom(target.path));
		const lineEnding = detectLineEnding(text);
		const normalized = normalizeToLF(text);

		const register = clipboard ?? {};
		const applyResult =
			fileOp?.kind === "rem"
				? this.#applyWithRecovery({
						section: target,
						canonicalPath,
						exists: read.exists,
						normalized,
						edits: [],
						clipboard: register,
					})

View on GitHub (pinned to 9690622007)

Solutions

  1. Change the move destination so it differs from the source path
  2. Compare canonical paths before issuing the move and skip the no-op operation
  3. Resolve symlinks/relative segments in the dest before constructing the fileOp
  4. Split the operation: apply the patch first, then move to a genuinely different path

Example fix

// before
{ kind: "move", dest: "./src/a.ts" } // target already src/a.ts → throws
// after
if (canonicalPath(dest) !== canonicalPath(target.path)) {
  applyOp({ kind: "move", dest });
}
Defensive patterns

Strategy: validation

Validate before calling

if (fileOp?.kind === "move" &&
    canonicalize(fileOp.dest) === canonicalize(target.path)) {
  return; // no-op move, skip
}

Try / catch

try {
  await patcher.prepare(target, fileOp);
} catch (e) {
  if (e.message.startsWith("MV destination is the same as")) {
    // treat as no-op or fix the dest in the calling config
  }
}

Prevention

When it happens

Trigger: Calling prepared/executeHashlineSingle/apply/preflight with a move fileOp whose dest normalizes (symlinks, relative segments, case) to the same path as target.path.

Common situations: Config or agent-generated mv step using './file' vs 'file' or a symlinked path pointing back at the source, template expansion producing identical src/dest, or a user renaming to the same name in a different case on a case-insensitive filesystem.

Related errors


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