hasura/graphql-engine · error

failed to copy directory tree as a fallback: %w

Error message

failed to copy directory tree as a fallback: %w

What it means

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.

Source

Thrown at cli/plugins/move.go:270

	if err != nil && !stderrors.Is(err, fs.ErrNotExist) {
		return errors.E(op, fmt.Errorf("error checking move target dir %q: %w", to, err))
	}

	if fi != nil && fi.IsDir() {
		err := os.RemoveAll(to)
		if err != nil {
			return errors.E(op, fmt.Errorf("error cleaning up dir %q: %w", to, err))
		}
	}

	err = os.Rename(from, to)
	// Fallback for invalid cross-device link (errno:18).
	if isCrossDeviceRenameErr(err) {
		copyErr := copyTree(from, to)
		if copyErr != nil {
			return errors.E(
				op,
				fmt.Errorf("failed to copy directory tree as a fallback: %w", copyErr),
			)
		}

		return nil
	}

	if err != nil {
		return errors.E(op, err)
	}

	return nil
}

// copyTree copies files or directories, recursively.
func copyTree(from, to string) (err error) {
	var op errors.Op = "plugins.copyTree"

	err = filepath.Walk(from, func(path string, info os.FileInfo, err error) error {

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Free space on the destination filesystem or raise the quota
  2. Set TMPDIR to a location on the same filesystem as the plugins install dir to avoid the copy entirely
  3. Verify read permission on all files in the temp staging dir (check the archive extraction)
  4. Retry the install after making room / fixing perms

Example fix

# before: EXDEV copy from tmpfs fails on big plugin
# after
TMPDIR=$HOME/tmp hasura plugins install my-plugin
Defensive patterns

Strategy: fallback

Validate before calling

var stat unix.Statfs_t
if err := unix.Statfs(filepath.Dir(installDir), &stat); err == nil && stat.Bavail*uint64(stat.Bsize) < neededBytes {
    return errors.New("insufficient space for copy fallback")
}

Try / catch

if err := installPlugin(p); err != nil && strings.Contains(err.Error(), "copy directory tree as a fallback") {
    // free space on dest, or set TMPDIR to same device, then retry
}

Prevention

When it happens

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

Common situations: Small tmpfs /tmp exhausting space while copying a large plugin binary; restrictive umask/permissions on extracted files; NFS/home quotas exceeded.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/d9ff118bfd7e9e4c. Report an issue: GitHub.