{"record":{"id":"d9ff118bfd7e9e4c","repo":"hasura/graphql-engine","slug":"failed-to-copy-directory-tree-as-a-fallback-w","errorCode":null,"errorMessage":"failed to copy directory tree as a fallback: %w","messagePattern":"failed to copy directory tree as a fallback: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/plugins/move.go","lineNumber":270,"sourceCode":"\tif err != nil && !stderrors.Is(err, fs.ErrNotExist) {\n\t\treturn errors.E(op, fmt.Errorf(\"error checking move target dir %q: %w\", to, err))\n\t}\n\n\tif fi != nil && fi.IsDir() {\n\t\terr := os.RemoveAll(to)\n\t\tif err != nil {\n\t\t\treturn errors.E(op, fmt.Errorf(\"error cleaning up dir %q: %w\", to, err))\n\t\t}\n\t}\n\n\terr = os.Rename(from, to)\n\t// Fallback for invalid cross-device link (errno:18).\n\tif isCrossDeviceRenameErr(err) {\n\t\tcopyErr := copyTree(from, to)\n\t\tif copyErr != nil {\n\t\t\treturn errors.E(\n\t\t\t\top,\n\t\t\t\tfmt.Errorf(\"failed to copy directory tree as a fallback: %w\", copyErr),\n\t\t\t)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif err != nil {\n\t\treturn errors.E(op, err)\n\t}\n\n\treturn nil\n}\n\n// copyTree copies files or directories, recursively.\nfunc copyTree(from, to string) (err error) {\n\tvar op errors.Op = \"plugins.copyTree\"\n\n\terr = filepath.Walk(from, func(path string, info os.FileInfo, err error) error {","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/plugins/move.go#L252-L288","documentation":"When os.Rename fails with a cross-device error (EXDEV, errno 18), renameOrCopy falls back to copyTree to copy the directory tree from source to destination; copyTree itself failed. So the move spans filesystems (e.g. /tmp tmpfs → home dir) and the recursive copy hit an error such as ENOSPC, EACCES, or an unreadable source file.","triggerScenarios":"Staged temp dir and install dir on different devices, and copyTree fails partway: destination disk full, permission mismatch when recreating file modes, or a source file that cannot be read.","commonSituations":"Small tmpfs /tmp exhausting space while copying a large plugin binary; restrictive umask/permissions on extracted files; NFS/home quotas exceeded.","solutions":["Free space on the destination filesystem or raise the quota","Set TMPDIR to a location on the same filesystem as the plugins install dir to avoid the copy entirely","Verify read permission on all files in the temp staging dir (check the archive extraction)","Retry the install after making room / fixing perms"],"exampleFix":"# before: EXDEV copy from tmpfs fails on big plugin\n# after\nTMPDIR=$HOME/tmp hasura plugins install my-plugin","handlingStrategy":"fallback","validationCode":"var stat unix.Statfs_t\nif err := unix.Statfs(filepath.Dir(installDir), &stat); err == nil && stat.Bavail*uint64(stat.Bsize) < neededBytes {\n    return errors.New(\"insufficient space for copy fallback\")\n}","typeGuard":null,"tryCatchPattern":"if err := installPlugin(p); err != nil && strings.Contains(err.Error(), \"copy directory tree as a fallback\") {\n    // free space on dest, or set TMPDIR to same device, then retry\n}","preventionTips":["Place TMPDIR on the same filesystem as the install dir","Keep at least archive-size free space before installs","Check extracted file permissions are readable by the running user"],"tags":["go","filesystem","cross-device","disk-full"],"backgroundTag":"cross-device-copy-failed","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}