yorukot/superfile · error

failed to remove source after move: %w

Error message

failed to remove source after move: %w

What it means

pasteDir wraps os.RemoveAll(src) failures when finishing a cross-device cut (move) that fell back to a manual copy. The move copied successfully but cleanup of the original failed, so the source remains and the wrapped cause (usually EPERM/EBUSY/ENOTEMPTY) is reported.

Source

Thrown at src/internal/file_operations.go:195

		}

		relPath, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}
		newPath := filepath.Join(dst, relPath)
		return actualPasteOperation(info, path, newPath, cut, sameDev, p, processBarModel)
	})

	if err != nil {
		return err
	}

	// If this was a cut operation and we had to do a manual copy, remove the source
	if cut && !sameDev {
		err = os.RemoveAll(src)
		if err != nil {
			return fmt.Errorf("failed to remove source after move: %w", err)
		}
	}

	return nil
}

func actualPasteOperation(info os.FileInfo, path string, newPath string, cut bool, sameDev bool,
	p *processbar.Process, processBarModel *processbar.Model) error {
	var err error
	if info.IsDir() {
		// TODO - this is likely not needed because we did
		// dst, err := renameIfDuplicate(dst) above
		newPath, err = renameIfDuplicate(newPath)
		if err != nil {
			return err
		}
		err = os.MkdirAll(newPath, info.Mode())
		return err

View on GitHub (pinned to b72f550bc6)

Solutions

  1. Close applications holding files in the source directory open (editors, indexers, terminals cd'd into it), then retry the move.
  2. Check write/execute permissions on the source directory and its contents (chmod -R u+w).
  3. Remove the leftover source manually once unlocked: rm -rf <src> (verify the copy first).
  4. Same-device moves avoid this path entirely: paste onto the same filesystem so rename() is used instead of copy+delete.

Example fix

// before
if cut && !sameDev {
    err = os.RemoveAll(src)
    if err != nil {
        return fmt.Errorf("failed to remove source after move: %w", err)
    }
}
// after
if cut && !sameDev {
    if err := os.RemoveAll(src); err != nil {
        return fmt.Errorf("move succeeded but could not delete source %s (is a file open elsewhere?): %w", src, err)
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// cross-device move will copy+delete; make sure no process holds the tree
// (best effort) verify write access to source root
if f, err := os.OpenFile(src, os.O_RDONLY, 0); err != nil {
    return fmt.Errorf("source may not be removable: %w", err)
} else {
    f.Close()
}

Try / catch

if err := ops.Paste(src, dst, cut=true); err != nil {
    if strings.Contains(err.Error(), "failed to remove source after move") {
        // copy succeeded; ask user to close apps holding source files, then rm -rf manually
    }
    return err
}

Prevention

When it happens

Trigger: A cut-paste across filesystems (rename fails EXDEV, so pasteDir does copy+delete) where the source cannot be removed: a file inside is held open (EBUSY on Windows), permission denied, or a new file appeared in the source dir making it non-empty for a non-recursive failure.

Common situations: Moving directories between drives/partitions (e.g. home -> USB) while an editor or indexer holds files open; moving dirs with files owned by other users; Windows AV scanners locking files.

Related errors


AI-assisted analysis of yorukot/superfile@b72f550bc6 (2026-09-01). Data as JSON: /api/errors/093b0666c3225f29. Report an issue: GitHub.