{"record":{"id":"27c77ab3ad9ed528","repo":"can1357/oh-my-pi","slug":"mv-destination-is-the-same-as-target-path","errorCode":null,"errorMessage":"MV destination is the same as ${target.path}.","messagePattern":"MV destination is the same as (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/hashline/src/patcher.ts","lineNumber":389,"sourceCode":"\t\t\t\t\tpathRecoveredFromTagMessage(target.path, recovered.section.path, target.fileHash as string),\n\t\t\t\t);\n\t\t\t\ttarget = recovered.section;\n\t\t\t\tcanonicalPath = recovered.canonicalPath;\n\t\t\t\tread = await this.#tryRead(target.path);\n\t\t\t}\n\t\t}\n\n\t\t// Gate the final (possibly recovered) target before any write work, so\n\t\t// an unrecoverable read-only target (e.g. a plan-mode working-tree path)\n\t\t// fails with the write guard rather than a misleading \"file not found\".\n\t\tawait this.fs.preflightWrite(target.path, { fileOp });\n\n\t\tif (!read.exists) {\n\t\t\tthrow new Error(`File not found: ${target.path}. Use the write tool to create new files.`);\n\t\t}\n\n\t\tif (fileOp?.kind === \"move\" && this.fs.canonicalPath(fileOp.dest) === canonicalPath) {\n\t\t\tthrow new Error(`MV destination is the same as ${target.path}.`);\n\t\t}\n\n\t\tconst { bom: bomFromText, text } = stripBom(read.rawContent);\n\t\tconst bom = bomFromText || (await this.#readBinaryBom(target.path));\n\t\tconst lineEnding = detectLineEnding(text);\n\t\tconst normalized = normalizeToLF(text);\n\n\t\tconst register = clipboard ?? {};\n\t\tconst applyResult =\n\t\t\tfileOp?.kind === \"rem\"\n\t\t\t\t? this.#applyWithRecovery({\n\t\t\t\t\t\tsection: target,\n\t\t\t\t\t\tcanonicalPath,\n\t\t\t\t\t\texists: read.exists,\n\t\t\t\t\t\tnormalized,\n\t\t\t\t\t\tedits: [],\n\t\t\t\t\t\tclipboard: register,\n\t\t\t\t\t})","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/hashline/src/patcher.ts#L371-L407","documentation":"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.","triggerScenarios":"Calling prepared/executeHashlineSingle/apply/preflight with a move fileOp whose dest normalizes (symlinks, relative segments, case) to the same path as target.path.","commonSituations":"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.","solutions":["Change the move destination so it differs from the source path","Compare canonical paths before issuing the move and skip the no-op operation","Resolve symlinks/relative segments in the dest before constructing the fileOp","Split the operation: apply the patch first, then move to a genuinely different path"],"exampleFix":"// before\n{ kind: \"move\", dest: \"./src/a.ts\" } // target already src/a.ts → throws\n// after\nif (canonicalPath(dest) !== canonicalPath(target.path)) {\n  applyOp({ kind: \"move\", dest });\n}","handlingStrategy":"validation","validationCode":"if (fileOp?.kind === \"move\" &&\n    canonicalize(fileOp.dest) === canonicalize(target.path)) {\n  return; // no-op move, skip\n}","typeGuard":null,"tryCatchPattern":"try {\n  await patcher.prepare(target, fileOp);\n} catch (e) {\n  if (e.message.startsWith(\"MV destination is the same as\")) {\n    // treat as no-op or fix the dest in the calling config\n  }\n}","preventionTips":["Canonicalize both paths (resolve symlinks, . and ..) before issuing a move","Skip move ops when src and dest resolve identically","Warn users when a rename destination matches the source case-insensitively","Generate move destinations programmatically rather than from raw template strings"],"tags":["file-operations","validation","move","patching"],"backgroundTag":"mv-source-equals-destination","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}