{"record":{"id":"7887142248a652e9","repo":"AlistGo/alist","slug":"can-t-move-files-between-two-storages-try-to-copy","errorCode":null,"errorMessage":"can't move files between two storages, try to copy","messagePattern":"can't move files between two storages, try to copy","errorType":"exception","errorClass":"MoveBetweenTwoStorages","httpStatus":null,"severity":"warning","filePath":"internal/errs/errors.go","lineNumber":15,"sourceCode":"package errs\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\tpkgerr \"github.com/pkg/errors\"\n)\n\nvar (\n\tNotImplement = errors.New(\"not implement\")\n\tNotSupport   = errors.New(\"not support\")\n\tRelativePath = errors.New(\"access using relative path is not allowed\")\n\n\tMoveBetweenTwoStorages = errors.New(\"can't move files between two storages, try to copy\")\n\tUploadNotSupported     = errors.New(\"upload not supported\")\n\n\tMetaNotFound     = errors.New(\"meta not found\")\n\tStorageNotFound  = errors.New(\"storage not found\")\n\tStreamIncomplete = errors.New(\"upload/download stream incomplete, possible network issue\")\n\tStreamPeekFail   = errors.New(\"StreamPeekFail\")\n\n\tUnknownArchiveFormat      = errors.New(\"unknown archive format\")\n\tWrongArchivePassword      = errors.New(\"wrong archive password\")\n\tDriverExtractNotSupported = errors.New(\"driver extraction not supported\")\n)\n\n// NewErr wrap constant error with an extra message\n// use errors.Is(err1, StorageNotFound) to check if err belongs to any internal error\nfunc NewErr(err error, format string, a ...any) error {\n\treturn fmt.Errorf(\"%w; %s\", err, fmt.Sprintf(format, a...))\n}\n","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/errs/errors.go#L1-L33","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nerr := fsMove(ctx, \"/cloud-a/docs/x.txt\", \"/cloud-b/docs/x.txt\")\n\n// after\ncopyErr := fsCopy(ctx, \"/cloud-a/docs/x.txt\", \"/cloud-b/docs/x.txt\")\nif copyErr != nil { return copyErr }\nerr := fsRemove(ctx, \"/cloud-a/docs/x.txt\")","handlingStrategy":"fallback","validationCode":"if srcStoragePath.Mount != dstStoragePath.Mount { return errs.MoveBetweenTwoStorages }","typeGuard":"func isCrossStorageMove(err error) bool { return errors.Is(err, errs.MoveBetweenTwoStorages) }","tryCatchPattern":"err := fsMove(ctx, srcPath, dstPath)\nif errors.Is(err, errs.MoveBetweenTwoStorages) {\n    if err := fsCopy(ctx, srcPath, dstPath); err != nil { return err }\n    return fsRemove(ctx, srcPath)\n}","preventionTips":["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"],"tags":["filesystem","move","storage-boundary","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}