{"record":{"id":"a41285dd5129931a","repo":"vxcontrol/pentagi","slug":"w-cannot-move-directory-into-itself","errorCode":null,"errorMessage":"%w: cannot move directory into itself","messagePattern":"%w: cannot move directory into itself","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"backend/pkg/server/services/resources.go","lineNumber":935,"sourceCode":"\t}\n\n\tupdated, err := updateMovedResource(tx, src, targetPath, time.Now())\n\tif err != nil {\n\t\treturn result, err\n\t}\n\tresult.Updated = append(result.Updated, convertResource(updated))\n\treturn result, nil\n}\n\nfunc (s *ResourceService) moveDirResource(\n\ttx *gorm.DB,\n\tuid uint64,\n\tsrcPath, dstPath string,\n\tforce bool,\n) (moveResourceResult, error) {\n\tresult := moveResourceResult{}\n\tif resources.PathHasPrefix(dstPath, srcPath) {\n\t\treturn result, fmt.Errorf(\"%w: cannot move directory into itself\", errResourceInvalid)\n\t}\n\t// Moving a directory to root (dstPath==\"\") means placing it at root level\n\tif dstPath == \"\" {\n\t\tdstPath = path.Base(srcPath)\n\t}\n\n\tcreatedParents, deletedParents, orphanHashes, err := ensureResourceDirs(tx, uid, resources.ParentDir(dstPath), force)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\tresult.Added = append(result.Added, convertResources(createdParents)...)\n\tresult.DeletedBefore = append(result.DeletedBefore, convertResources(deletedParents)...)\n\tresult.OrphanHashes = append(result.OrphanHashes, orphanHashes...)\n\n\tdest, destExists, err := findResourceByPath(tx, uid, dstPath)\n\tif err != nil {\n\t\treturn result, err\n\t}","sourceCodeStart":917,"sourceCodeEnd":953,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/services/resources.go#L917-L953","documentation":"moveDirResource rejects a move where the destination path lies inside the source directory (PathHasPrefix), which would create a cycle and corrupt the tree. It wraps errResourceInvalid so callers can classify it as caller error rather than a server failure. No changes are made.","triggerScenarios":"Calling MoveResource with dstPath='/a/b' and srcPath='/a' (or dstPath equal to srcPath), e.g. moving directory /a into /a/sub.","commonSituations":"Recursive filesystem walks that try to move a parent into its own child; UI allowing a folder to be dropped into one of its descendants; scripts building destination paths by string concatenation without prefix checks.","solutions":["Validate that dstPath does not start with srcPath + \"/\" before calling","Match errors.Is(err, errResourceInvalid) and reject the operation in the UI","Normalize both paths (clean trailing slashes) before comparison to avoid false negatives"],"exampleFix":"// before\nsvc.MoveResource(ctx, uid, []Source{{Path: \"/a\"}}, \"/a/sub\", false)\n// after\nif resources.PathHasPrefix(\"/a/sub\", \"/a\") {\n    return fmt.Errorf(\"cannot move %q into itself\", \"/a\")\n}\nsvc.MoveResource(ctx, uid, []Source{{Path: \"/a\"}}, \"/a/sub\", false)","handlingStrategy":"validation","validationCode":"if resources.PathHasPrefix(dstPath, srcPath) {\n    return fmt.Errorf(\"refusing move: %q into %q\", srcPath, dstPath)\n}","typeGuard":null,"tryCatchPattern":"err := svc.MoveResource(ctx, uid, []Source{{Path: srcPath}}, dstPath, false)\nif errors.Is(err, services.ErrResourceInvalid) {\n    // reject in UI: cannot move folder into itself\n}","preventionTips":["Check path prefixes in any recursive move logic","Disable drop targets that are descendants of the dragged folder","Clean/normalize paths before validation"],"tags":["resources","move","directory","cycle","invalid-argument"],"backgroundTag":"directory-move-into-itself","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}