{"record":{"id":"5f7bd5d8eb2cacfd","repo":"vxcontrol/pentagi","slug":"w-cannot-copy-directory-q-into-itself","errorCode":null,"errorMessage":"%w: cannot copy directory %q into itself","messagePattern":"%w: cannot copy directory %q into itself","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/server/services/resources.go","lineNumber":1361,"sourceCode":"\n\t// ── Within-batch target-basename conflict check ───────────────────────────\n\tbasenameToSrc := make(map[string]string, len(srcs))\n\tfor _, src := range srcs {\n\t\tif prev, conflict := basenameToSrc[src.Name]; conflict {\n\t\t\ttx.Rollback()\n\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-copy guard: copying a directory into itself.\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 copy 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 {","sourceCodeStart":1343,"sourceCodeEnd":1379,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/services/resources.go#L1343-L1379","documentation":"copyMultipleSources rejects a batch where a source directory is an ancestor of (or equal to) the destination path — the copy would recursively contain itself, producing infinite nesting. The guard uses resources.PathHasPrefix on the destination, returns errResourceInvalid, and rolls back the transaction.","triggerScenarios":"Multi-source copy with destination inside or equal to one of the source directories, e.g. sources ['docs'] and destination 'docs/archive', or destination 'docs' itself.","commonSituations":"UI letting users pick the currently-open folder as the copy target; scripting a copy of a project into its own subfolder; off-by-one path joins that place dstPath under a source.","solutions":["Choose a destination outside every source directory in the batch.","If a copy into the folder is truly needed, copy the folder's contents to a sibling directory instead.","Validate client-side: reject when dstPath === src.path or dstPath starts with src.path + '/'.","For single-directory duplication use single-source mode, which handles the self-copy case differently."],"exampleFix":"// before\ncopy({ sources: [\"docs\"], destination: \"docs/archive\" }) // invalid: docs into itself\n// after\ncopy({ sources: [\"docs\"], destination: \"backup/docs\" })","handlingStrategy":"validation","validationCode":"for _, s := range dirSources {\n    if dst == s || strings.HasPrefix(dst, s+\"/\") {\n        return fmt.Errorf(\"destination %q is inside source %q\", dst, s)\n    }\n}","typeGuard":"func dstInsideSource(dst, src string) bool {\n    return dst == src || strings.HasPrefix(dst, strings.TrimSuffix(src, \"/\")+\"/\")\n}","tryCatchPattern":"err := copyMulti(ctx, sources, dst)\nif err != nil && errors.Is(err, errResourceInvalid) {\n    // choose a destination outside all source directories and retry\n}","preventionTips":["Disable the current folder as a copy destination in folder pickers.","Validate dst not under any source before submit.","When duplicating a tree, copy to a sibling path.","Write a shared helper for the ancestor check used everywhere."],"tags":["invalid-argument","recursion-guard","directory","batch"],"backgroundTag":"copy-into-itself","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}