AlistGo/alist · warning · MoveBetweenTwoStorages
can't move files between two storages, try to copy
Error message
can't move files between two storages, try to copy
What it means
errs.MoveBetweenTwoStorages is returned when a move operation is requested across two different storage mounts. Filesystem-level move between storages is not a single atomic remote operation, so the framework refuses it and the message itself tells you the remedy: copy instead.
Source
Thrown at internal/errs/errors.go:15
package errs
import (
"errors"
"fmt"
pkgerr "github.com/pkg/errors"
)
var (
NotImplement = errors.New("not implement")
NotSupport = errors.New("not support")
RelativePath = errors.New("access using relative path is not allowed")
MoveBetweenTwoStorages = errors.New("can't move files between two storages, try to copy")
UploadNotSupported = errors.New("upload not supported")
MetaNotFound = errors.New("meta not found")
StorageNotFound = errors.New("storage not found")
StreamIncomplete = errors.New("upload/download stream incomplete, possible network issue")
StreamPeekFail = errors.New("StreamPeekFail")
UnknownArchiveFormat = errors.New("unknown archive format")
WrongArchivePassword = errors.New("wrong archive password")
DriverExtractNotSupported = errors.New("driver extraction not supported")
)
// NewErr wrap constant error with an extra message
// use errors.Is(err1, StorageNotFound) to check if err belongs to any internal error
func NewErr(err error, format string, a ...any) error {
return fmt.Errorf("%w; %s", err, fmt.Sprintf(format, a...))
}
View on GitHub (pinned to 843d9dc814)
Solutions
- Use copy: copy the object to the destination storage, then delete the source after verifying the copy
- Verify the destination path stays inside the same storage if a true move was intended (fix the typo)
- For frequent cross-storage needs, script copy+delete explicitly with error checks between steps
- Some frontends do this automatically — enable that option if available
Example fix
// before
err := fsMove(ctx, "/cloud-a/docs/x.txt", "/cloud-b/docs/x.txt")
// after
copyErr := fsCopy(ctx, "/cloud-a/docs/x.txt", "/cloud-b/docs/x.txt")
if copyErr != nil { return copyErr }
err := fsRemove(ctx, "/cloud-a/docs/x.txt") Defensive patterns
Strategy: fallback
Validate before calling
if srcStoragePath.Mount != dstStoragePath.Mount { return errs.MoveBetweenTwoStorages } Type guard
func isCrossStorageMove(err error) bool { return errors.Is(err, errs.MoveBetweenTwoStorages) } Try / catch
err := fsMove(ctx, srcPath, dstPath)
if errors.Is(err, errs.MoveBetweenTwoStorages) {
if err := fsCopy(ctx, srcPath, dstPath); err != nil { return err }
return fsRemove(ctx, srcPath)
} Prevention
- Validate source and destination share a mount before attempting move
- In UI, offer 'copy' semantics for cross-storage drag-and-drop
- Verify destination paths for typos that slip across a mount boundary
When it happens
Trigger: Calling Move/Rename with a source object on storage A and a destination path resolving to storage B — e.g. moving /mnt/cloud-a/file.txt to /mnt/cloud-b/. Detected during path resolution when src and dst storage identities differ.
Common situations: Users dragging files between two mounted storages in the web UI; scripts using a single virtual tree spanning multiple backends; renaming with a path that accidentally crosses a mount boundary (typos in the destination root).
Related errors
- cannot move parent dir to child
- cannot move a submodule
- cannot move in place
- cannot move parent dir to child
- root folder %s not exists
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/7887142248a652e9.
Report an issue: GitHub.