coreybutler/nvm-windows · error

failed to read source directory

Error message

failed to read source directory: %w

What it means

First step of copyDir(): os.ReadDir(old) lists the source directory. This error means the source could not be listed — it does not exist, it is not a directory (a file occupies the name), or the user lacks list permission. No destination writes have happened when this fires.

Solutions

  1. Verify the source directory still exists immediately before the operation (dir <path>).
  2. Ensure only one nvm command runs at a time to avoid races deleting the tree.
  3. Run elevated if the wrapped error is 'Access is denied'.
  4. Fix the NVM_HOME setting if it points at a stale/inconsistent layout.
Defensive patterns

Strategy: validation

Validate before calling

func isReadableDir(p string) bool {
    info, err := os.Stat(p)
    return err == nil && info.IsDir()
}
if !isReadableDir(src) { return fmt.Errorf("source %s is not a readable directory", src) }

Type guard

func isReadableDir(p string) bool {
    info, err := os.Stat(p)
    return err == nil && info.IsDir()
}

Try / catch

if err := copyTree(old, new); err != nil && strings.Contains(err.Error(), "read source directory") {
    if os.IsNotExist(errors.Unwrap(err)) { /* source deleted mid-run: restart operation */ }
}

Prevention

When it happens

Trigger: copyDir called with a path that was deleted between the parent's ReadDir and the recursive call, a path that names a regular file, or a directory with a denying ACL.

Common situations: Concurrent nvm operations pruning version directories; source directory moved/renamed manually mid-upgrade; NVM_HOME misconfigured to a path where a file shadows a directory name.

Related errors


AI-assisted analysis of coreybutler/nvm-windows@5b18223ca1 (2026-08-15). Data as JSON: /api/errors/5974da79572046c6. Report an issue: GitHub.

Appendix: source

Thrown at src/utility/rename.go:90

	// Copy file permissions
	info, err := srcFile.Stat()
	if err != nil {
		return fmt.Errorf("failed to get source file info: %w", err)
	}
	err = os.Chmod(new, info.Mode())
	if err != nil {
		return fmt.Errorf("failed to set permissions on destination file: %w", err)
	}

	return nil
}

// copyDir recursively copies a directory from old path to new path.
func copyDir(old, new string) error {
	entries, err := os.ReadDir(old)
	if err != nil {
		return fmt.Errorf("failed to read source directory: %w", err)
	}

	// Ensure destination directory exists
	err = os.MkdirAll(new, os.ModePerm)
	if err != nil {
		return fmt.Errorf("failed to create destination directory: %w", err)
	}

	for _, entry := range entries {
		srcPath := filepath.Join(old, entry.Name())
		destPath := filepath.Join(new, entry.Name())

		if entry.IsDir() {
			err = copyDir(srcPath, destPath)
			if err != nil {
				return fmt.Errorf("failed to copy subdirectory: %w", err)
			}
		} else {

View on GitHub (pinned to 5b18223ca1)