{"record":{"id":"f06446026efe689a","repo":"AlistGo/alist","slug":"cannot-move-to-same-parent-directory","errorCode":null,"errorMessage":"cannot move to same parent directory","messagePattern":"cannot move to same parent directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"drivers/proton_drive/util.go","lineNumber":690,"sourceCode":"func (d *ProtonDrive) executeMoveAPI(ctx context.Context, linkID string, req MoveRequest) error {\n\t//fmt.Printf(\"DEBUG Move Request - Name: %s\\n\", req.Name)\n\t//fmt.Printf(\"DEBUG Move Request - Hash: %s\\n\", req.Hash)\n\t//fmt.Printf(\"DEBUG Move Request - OriginalHash: %s\\n\", req.OriginalHash)\n\t//fmt.Printf(\"DEBUG Move Request - ParentLinkID: %s\\n\", req.ParentLinkID)\n\n\t//fmt.Printf(\"DEBUG Move Request - Name length: %d\\n\", len(req.Name))\n\t//fmt.Printf(\"DEBUG Move Request - NameSignatureEmail: %s\\n\", req.NameSignatureEmail)\n\t//fmt.Printf(\"DEBUG Move Request - ContentHash: %v\\n\", req.ContentHash)\n\t//fmt.Printf(\"DEBUG Move Request - NodePassphrase length: %d\\n\", len(req.NodePassphrase))\n\t//fmt.Printf(\"DEBUG Move Request - NodePassphraseSignature length: %d\\n\", len(req.NodePassphraseSignature))\n\n\t//fmt.Printf(\"DEBUG Move Request - SrcLinkID: %s\\n\", linkID)\n\t//fmt.Printf(\"DEBUG Move Request - DstParentLinkID: %s\\n\", req.ParentLinkID)\n\t//fmt.Printf(\"DEBUG Move Request - ShareID: %s\\n\", d.MainShare.ShareID)\n\n\tsrcLink, _ := d.getLink(ctx, linkID)\n\tif srcLink != nil && srcLink.ParentLinkID == req.ParentLinkID {\n\t\treturn fmt.Errorf(\"cannot move to same parent directory\")\n\t}\n\n\tmoveURL := fmt.Sprintf(d.apiBase+\"/drive/v2/volumes/%s/links/%s/move\",\n\t\td.MainShare.VolumeID, linkID)\n\n\treqBody, err := json.Marshal(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal move request: %w\", err)\n\t}\n\n\thttpReq, err := http.NewRequestWithContext(ctx, \"PUT\", moveURL, bytes.NewReader(reqBody))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create HTTP request: %w\", err)\n\t}\n\n\thttpReq.Header.Set(\"Authorization\", \"Bearer \"+d.credentials.AccessToken)\n\thttpReq.Header.Set(\"Accept\", d.protonJson)\n\thttpReq.Header.Set(\"X-Pm-Appversion\", d.webDriveAV)","sourceCodeStart":672,"sourceCodeEnd":708,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/proton_drive/util.go#L672-L708","documentation":"executeMoveAPI refuses the operation up front: the source link's current ParentLinkID equals the requested destination ParentLinkID, so the move would be a no-op into the same folder. Note the link is fetched with srcLink, _ := d.getLink(ctx, linkID) — if that fetch errors, srcLink is nil and the check is silently skipped, deferring failure to the server.","triggerScenarios":"DirectMove called where dstDir is already the object's parent (common when a UI issues move on drop-to-same-folder, or path resolution of '/' maps to the same root); duplicate move retries after a partial success.","commonSituations":"Frontends that optimistically call move on every drag-drop; retry logic re-running an already-completed move; root-path handling where dstParentLinkID resolves to the current parent.","solutions":["Treat as a no-op success in the caller: compare parent IDs before calling DirectMove and return the existing object","In the driver, return a sentinel (e.g. ErrSameParent) or model.Obj nil-error so callers can distinguish no-op from failure","Handle the getLink error instead of discarding it, so a stale link does not bypass the check"],"exampleFix":"// before\nsrcLink, _ := d.getLink(ctx, linkID)\nif srcLink != nil && srcLink.ParentLinkID == req.ParentLinkID {\n\treturn fmt.Errorf(\"cannot move to same parent directory\")\n}\n// after\nsrcLink, err := d.getLink(ctx, linkID)\nif err != nil { return fmt.Errorf(\"failed to get link: %w\", err) }\nif srcLink.ParentLinkID == req.ParentLinkID {\n\treturn ErrSameParent // callers treat as no-op success\n}","handlingStrategy":"validation","validationCode":"// before calling DirectMove, compare parents yourself:\nsrcLink, _ := d.getLink(ctx, srcID)\ndstID := resolveDstParentID(dstDir)\nif srcLink != nil && srcLink.ParentLinkID == dstID {\n    return srcObj, nil // no-op move, succeed silently\n}","typeGuard":null,"tryCatchPattern":"if err := d.DirectMove(ctx, src, dst); err != nil {\n    if strings.Contains(err.Error(), \"cannot move to same parent directory\") {\n        return src, nil // treat as no-op success\n    }\n}","preventionTips":["Guard drag-drop UIs from issuing moves to the same folder","Handle the discarded getLink error in your fork so the check cannot be silently skipped"],"tags":["proton-drive","move","validation","no-op"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}