{"record":{"id":"194aa897bfef0d8e","repo":"AlistGo/alist","slug":"cannot-move-parent-dir-to-child","errorCode":null,"errorMessage":"cannot move parent dir to child","messagePattern":"cannot move parent dir to child","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/github/driver.go","lineNumber":252,"sourceCode":"\tcommitMessage, err := getMessage(d.mkdirMsgTmpl, &MessageTemplateVars{\n\t\tUserName:   getUsername(ctx),\n\t\tObjName:    dirName,\n\t\tObjPath:    stdpath.Join(parentDir.GetPath(), dirName),\n\t\tParentName: parentDir.GetName(),\n\t\tParentPath: parentDir.GetPath(),\n\t}, \"mkdir\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn d.commit(commitMessage, rootSha)\n}\n\nfunc (d *Github) Move(ctx context.Context, srcObj, dstDir model.Obj) error {\n\tif !d.isOnBranch {\n\t\treturn errors.New(\"cannot write to non-branch reference\")\n\t}\n\tif strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {\n\t\treturn errors.New(\"cannot move parent dir to child\")\n\t}\n\td.commitMutex.Lock()\n\tdefer d.commitMutex.Unlock()\n\n\tvar rootSha string\n\tif strings.HasPrefix(dstDir.GetPath(), stdpath.Dir(srcObj.GetPath())) { // /aa/1 -> /aa/bb/\n\t\tdstOldSha, dstNewSha, ancestorOldSha, srcParentTree, err := d.copyWithoutRenewTree(srcObj, dstDir)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsrcParentPath := stdpath.Dir(srcObj.GetPath())\n\t\tdstRest := dstDir.GetPath()[len(srcParentPath):]\n\t\tif dstRest[0] == '/' {\n\t\t\tdstRest = dstRest[1:]\n\t\t}\n\t\tdstNextName, _, _ := strings.Cut(dstRest, \"/\")\n\t\tdstNextPath := stdpath.Join(srcParentPath, dstNextName)","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/github/driver.go#L234-L270","documentation":"Move() rejects moving a directory into its own subtree: strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) means the destination lives inside the source (e.g. moving /docs into /docs/sub). Such a move is either a no-op or would create a cycle in the git tree, so it is refused. drivers/github/driver.go:252.","triggerScenarios":"Moving /a to /a/b, or /a to /a itself (path prefix collision), through the OpenList UI or API against the GitHub driver.","commonSituations":"User drags a folder into one of its own children by mistake; destination path typo'd to start with the source path; automation computing destination paths from source paths without excluding descendants.","solutions":["Choose a destination outside the source directory's subtree (e.g. /a -> /b/a).","If the intent was renaming, use Rename on the object instead of Move.","Fix automation logic to reject dst paths with the src path as prefix."],"exampleFix":"// before\nMove(src=/docs, dst=/docs/archive) // rejected\n\n// after\nMove(src=/docs, dst=/archive/docs) // valid","handlingStrategy":"validation","validationCode":"if strings.HasPrefix(dstDir.GetPath()+\"/\", srcObj.GetPath()+\"/\") {\n    return errors.New(\"cannot move a directory into itself\")\n}","typeGuard":"func isMoveIntoSelf(src, dst model.Obj) bool {\n    return strings.HasPrefix(stdpath.Clean(dst.GetPath())+\"/\", stdpath.Clean(src.GetPath())+\"/\")\n}","tryCatchPattern":null,"preventionTips":["Validate src/dst path relationships before issuing Move.","Use Rename when the goal is renaming, not relocating.","In UIs, filter the destination picker to exclude the source's subtree."],"tags":["github","move","path-validation","cycle","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}