{"record":{"id":"f5bfe2e30db5b5bf","repo":"vxcontrol/pentagi","slug":"w-source-and-destination-are-the-same-for-q","errorCode":null,"errorMessage":"%w: source and destination are the same for %q","messagePattern":"%w: source and destination are the same for %q","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"backend/pkg/server/services/resources.go","lineNumber":729,"sourceCode":"\t\t\treturn result, fmt.Errorf(\n\t\t\t\t\"%w: sources %q and %q share the same base name %q\",\n\t\t\t\terrResourceConflict, prev, src.Path, src.Name,\n\t\t\t)\n\t\t}\n\t\tbasenameToSrc[src.Name] = src.Path\n\t}\n\n\t// Self-move guard: no source may be a directory that contains dstPath.\n\tfor _, src := range srcs {\n\t\tif src.IsDir && resources.PathHasPrefix(dstPath, src.Path) {\n\t\t\ttx.Rollback()\n\t\t\treturn result, fmt.Errorf(\n\t\t\t\t\"%w: cannot move directory %q into itself\", errResourceInvalid, src.Path,\n\t\t\t)\n\t\t}\n\t\tif resources.FilePath(dstPath, src.Name) == src.Path {\n\t\t\ttx.Rollback()\n\t\t\treturn result, fmt.Errorf(\n\t\t\t\t\"%w: source and destination are the same for %q\", errResourceInvalid, src.Path,\n\t\t\t)\n\t\t}\n\t}\n\n\t// ── Ensure destination directory ──────────────────────────────────────────\n\tdest, destExists, err := findResourceByPath(tx, uid, dstPath)\n\tif err != nil {\n\t\ttx.Rollback()\n\t\treturn result, err\n\t}\n\tif destExists && !dest.IsDir {\n\t\ttx.Rollback()\n\t\treturn result, fmt.Errorf(\n\t\t\t\"%w: destination %q is a file; cannot move multiple sources into it\",\n\t\t\terrResourceConflict, dstPath,\n\t\t)\n\t}","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/services/resources.go#L711-L747","documentation":"This error is returned by moveMultipleSources when a requested source's resolved destination path (dstPath + source name) equals its current path, i.e. moving a resource onto itself. It wraps errResourceInvalid so callers can match the sentinel with errors.Is. The transaction is rolled back so no partial move is applied.","triggerScenarios":"Calling MoveResource with multiple sources where one source's target directory already contains (or resolves to) the same path, e.g. moving file 'a.txt' into directory '/' when it already lives at '/a.txt', or moving a dir into its own parent unchanged.","commonSituations":"Bulk drag-and-drop in a UI where a user drops a file onto the folder it already resides in; scripted bulk moves that compute destinations without checking current location; retries of an idempotent move after a prior success.","solutions":["Check that FilePath(dstPath, src.Name) != src.Path for each source before calling MoveResource","Filter out no-op entries from the source list client-side","Match errors.Is(err, errResourceInvalid) and treat as success/no-op if the move is meant to be idempotent"],"exampleFix":"// before\nres, err := svc.MoveResource(ctx, uid, sources, dstPath, false)\n// after\nfiltered := slices.DeleteFunc(sources, func(s Source) bool {\n    return resources.FilePath(dstPath, s.Name) == s.Path\n})\nres, err := svc.MoveResource(ctx, uid, filtered, dstPath, false)","handlingStrategy":"validation","validationCode":"for _, s := range sources {\n    if resources.FilePath(dstPath, s.Name) == s.Path {\n        return fmt.Errorf(\"no-op move: %q\", s.Path)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := svc.MoveResource(ctx, uid, sources, dstPath, false); err != nil {\n    if errors.Is(err, services.ErrResourceInvalid) {\n        // treat as no-op / skip\n    }\n}","preventionTips":["Filter no-op sources before bulk moves","Normalize paths before comparing","Design move APIs to tolerate idempotent retries"],"tags":["resources","move","invalid-argument","no-op"],"backgroundTag":"resource-move-onto-itself","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}