{"record":{"id":"5974da79572046c6","repo":"coreybutler/nvm-windows","slug":"failed-to-read-source-directory-w","errorCode":null,"errorMessage":"failed to read source directory: %w","messagePattern":"failed to read source directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/utility/rename.go","lineNumber":90,"sourceCode":"\n\t// Copy file permissions\n\tinfo, err := srcFile.Stat()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get source file info: %w\", err)\n\t}\n\terr = os.Chmod(new, info.Mode())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set permissions on destination file: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// copyDir recursively copies a directory from old path to new path.\nfunc copyDir(old, new string) error {\n\tentries, err := os.ReadDir(old)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read source directory: %w\", err)\n\t}\n\n\t// Ensure destination directory exists\n\terr = os.MkdirAll(new, os.ModePerm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination directory: %w\", err)\n\t}\n\n\tfor _, entry := range entries {\n\t\tsrcPath := filepath.Join(old, entry.Name())\n\t\tdestPath := filepath.Join(new, entry.Name())\n\n\t\tif entry.IsDir() {\n\t\t\terr = copyDir(srcPath, destPath)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to copy subdirectory: %w\", err)\n\t\t\t}\n\t\t} else {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/coreybutler/nvm-windows/blob/5b18223ca19ff50d707f35410dbc6bd440a9f74d/src/utility/rename.go#L72-L108","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the source directory still exists immediately before the operation (dir <path>).","Ensure only one nvm command runs at a time to avoid races deleting the tree.","Run elevated if the wrapped error is 'Access is denied'.","Fix the NVM_HOME setting if it points at a stale/inconsistent layout."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func isReadableDir(p string) bool {\n    info, err := os.Stat(p)\n    return err == nil && info.IsDir()\n}\nif !isReadableDir(src) { return fmt.Errorf(\"source %s is not a readable directory\", src) }","typeGuard":"func isReadableDir(p string) bool {\n    info, err := os.Stat(p)\n    return err == nil && info.IsDir()\n}","tryCatchPattern":"if err := copyTree(old, new); err != nil && strings.Contains(err.Error(), \"read source directory\") {\n    if os.IsNotExist(errors.Unwrap(err)) { /* source deleted mid-run: restart operation */ }\n}","preventionTips":["Validate the directory exists and is a directory before recursive copies.","Serialize nvm commands to prevent concurrent tree deletion.","Fix NVM_HOME when the version tree layout looks inconsistent."],"tags":["filesystem","readdir","copy","directory","race-condition"],"backgroundTag":null,"analyzedSha":"5b18223ca19ff50d707f35410dbc6bd440a9f74d","analyzedAt":"2026-08-15T10:06:51.428Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}