{"record":{"id":"6033111b6ac2de0f","repo":"AlistGo/alist","slug":"cannot-copy-parent-dir-to-child","errorCode":null,"errorMessage":"cannot copy parent dir to child","messagePattern":"cannot copy parent dir to child","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/github/driver.go","lineNumber":529,"sourceCode":"\t\tObjName:    srcObj.GetName(),\n\t\tObjPath:    srcObj.GetPath(),\n\t\tParentName: stdpath.Base(parentDir),\n\t\tParentPath: parentDir,\n\t\tTargetName: newName,\n\t\tTargetPath: stdpath.Join(parentDir, newName),\n\t}, \"rename\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn d.commit(message, rootSha)\n}\n\nfunc (d *Github) Copy(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 copy parent dir to child\")\n\t}\n\td.commitMutex.Lock()\n\tdefer d.commitMutex.Unlock()\n\n\tdstSha, newSha, _, _, err := d.copyWithoutRenewTree(srcObj, dstDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\trootSha, err := d.renewParentTrees(dstDir.GetPath(), dstSha, newSha, \"/\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tmessage, err := getMessage(d.copyMsgTmpl, &MessageTemplateVars{\n\t\tUserName:   getUsername(ctx),\n\t\tObjName:    srcObj.GetName(),\n\t\tObjPath:    srcObj.GetPath(),\n\t\tParentName: stdpath.Base(stdpath.Dir(srcObj.GetPath())),\n\t\tParentPath: stdpath.Dir(srcObj.GetPath()),","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/github/driver.go#L511-L547","documentation":"Copy() rejects copying a directory into its own subtree: strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) detects that the destination is inside the source, which would recursively nest the copy inside itself. The guard mirrors the one in Move(). drivers/github/driver.go:529.","triggerScenarios":"Copying /a to /a/b (destination path starts with the source path) through the UI or driver API.","commonSituations":"Drag-and-drop of a folder into its own child; automation computing dst as src + suffix without realizing the collision.","solutions":["Choose a destination outside the source subtree (e.g. /a -> /backup/a).","Fix caller logic to reject destinations prefixed by the source path.","If duplicating in place was intended, copy to a sibling path first, then move."],"exampleFix":"// before\nCopy(src=/docs, dst=/docs/copy) // rejected\n\n// after\nCopy(src=/docs, dst=/backup/docs) // valid","handlingStrategy":"validation","validationCode":"if strings.HasPrefix(dstDir.GetPath()+\"/\", srcObj.GetPath()+\"/\") {\n    return errors.New(\"cannot copy a directory into itself\")\n}","typeGuard":"func isCopyIntoSelf(src, dst model.Obj) bool {\n    return strings.HasPrefix(stdpath.Clean(dst.GetPath())+\"/\", stdpath.Clean(src.GetPath())+\"/\")\n}","tryCatchPattern":null,"preventionTips":["Validate src/dst prefix relationships before Copy.","Choose sibling or unrelated destinations for duplicates.","Exclude the source subtree in destination pickers."],"tags":["github","copy","path-validation","cycle","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}