vxcontrol/pentagi · error

%w: cannot copy directory %q into itself

Error message

%w: cannot copy directory %q into itself

What it means

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.

Source

Thrown at backend/pkg/server/services/resources.go:1361

	// ── Within-batch target-basename conflict check ───────────────────────────
	basenameToSrc := make(map[string]string, len(srcs))
	for _, src := range srcs {
		if prev, conflict := basenameToSrc[src.Name]; conflict {
			tx.Rollback()
			return result, fmt.Errorf(
				"%w: sources %q and %q share the same base name %q",
				errResourceConflict, prev, src.Path, src.Name,
			)
		}
		basenameToSrc[src.Name] = src.Path
	}

	// Self-copy guard: copying a directory into itself.
	for _, src := range srcs {
		if src.IsDir && resources.PathHasPrefix(dstPath, src.Path) {
			tx.Rollback()
			return result, fmt.Errorf(
				"%w: cannot copy directory %q into itself", errResourceInvalid, src.Path,
			)
		}
		if resources.FilePath(dstPath, src.Name) == src.Path {
			tx.Rollback()
			return result, fmt.Errorf(
				"%w: source and destination are the same for %q", errResourceInvalid, src.Path,
			)
		}
	}

	// ── Ensure destination directory ──────────────────────────────────────────
	dest, destExists, err := findResourceByPath(tx, uid, dstPath)
	if err != nil {
		tx.Rollback()
		return result, err
	}
	if destExists && !dest.IsDir {

View on GitHub (pinned to ea665308ba)

Solutions

  1. Choose a destination outside every source directory in the batch.
  2. If a copy into the folder is truly needed, copy the folder's contents to a sibling directory instead.
  3. Validate client-side: reject when dstPath === src.path or dstPath starts with src.path + '/'.
  4. For single-directory duplication use single-source mode, which handles the self-copy case differently.

Example fix

// before
copy({ sources: ["docs"], destination: "docs/archive" }) // invalid: docs into itself
// after
copy({ sources: ["docs"], destination: "backup/docs" })
Defensive patterns

Strategy: validation

Validate before calling

for _, s := range dirSources {
    if dst == s || strings.HasPrefix(dst, s+"/") {
        return fmt.Errorf("destination %q is inside source %q", dst, s)
    }
}

Type guard

func dstInsideSource(dst, src string) bool {
    return dst == src || strings.HasPrefix(dst, strings.TrimSuffix(src, "/")+"/")
}

Try / catch

err := copyMulti(ctx, sources, dst)
if err != nil && errors.Is(err, errResourceInvalid) {
    // choose a destination outside all source directories and retry
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/5f7bd5d8eb2cacfd. Report an issue: GitHub.