vxcontrol/pentagi · error

%w: destination %q is a file; cannot move multiple sources i

Error message

%w: destination %q is a file; cannot move multiple sources into it

What it means

moveMultipleSources refuses to proceed when the destination directory path already exists but is a regular file, since multiple sources cannot be moved 'into' a file. It wraps errResourceConflict so callers can distinguish conflicts from invalid input. The transaction is rolled back before returning.

Source

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

			)
		}
		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 {
		tx.Rollback()
		return result, fmt.Errorf(
			"%w: destination %q is a file; cannot move multiple sources into it",
			errResourceConflict, dstPath,
		)
	}
	if !destExists {
		createdDirs, deletedDirs, orphanHashes, dirErr := ensureResourceDirs(tx, uid, dstPath, force)
		if dirErr != nil {
			tx.Rollback()
			return result, dirErr
		}
		result.Added = append(result.Added, convertResources(createdDirs)...)
		result.DeletedBefore = append(result.DeletedBefore, convertResources(deletedDirs)...)
		result.OrphanHashes = append(result.OrphanHashes, orphanHashes...)
	}

	// ── Move each source into destination/<basename> ──────────────────────────
	// The inner move functions (moveFileResource / moveDirResource) call
	// ensureResourceDirs for the parent of the target path. Since dstPath is

View on GitHub (pinned to ea665308ba)

Solutions

  1. Verify the destination is an existing directory (or does not exist) before calling MoveResource
  2. Use a different destination path or rename the conflicting file first
  3. Match errResourceConflict in the caller and surface 'destination is a file' to the user

Example fix

// before
svc.MoveResource(ctx, uid, sources, dstPath, false)
// after
dest, exists := findPath(dstPath)
if exists && !dest.IsDir {
    return fmt.Errorf("destination %q is a file", dstPath)
}
svc.MoveResource(ctx, uid, sources, dstPath, false)
Defensive patterns

Strategy: validation

Validate before calling

dest, exists := findResourceByPath(dstPath)
if exists && !dest.IsDir {
    return fmt.Errorf("destination %q is a file", dstPath)
}

Try / catch

err := svc.MoveResource(ctx, uid, sources, dstPath, false)
if errors.Is(err, services.ErrResourceConflict) {
    // prompt user to pick another destination
}

Prevention

When it happens

Trigger: Calling MoveResource with a dstPath that resolves to an existing file resource instead of a directory, e.g. dstPath='/notes' where '/notes' is a stored file; a typo where the user intended '/notes/' as a directory.

Common situations: A file and directory share the same name after a prior rename; client passes a selected file's path instead of its parent folder as the drop target; stale UI cache shows a directory that is now a file.

Related errors


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