coreybutler/nvm-windows · error

failed to read source directory: %w

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.

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)

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.