{"record":{"id":"3682e2235b952e15","repo":"apache/answer","slug":"failed-to-read-plugins-dir-w","errorCode":null,"errorMessage":"failed to read plugins dir: %w","messagePattern":"failed to read plugins dir: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cli/build.go","lineNumber":351,"sourceCode":"\t// The node_modules folder generated during development will interfere packaging, so it needs to be ignored.\n\tif err = copyDirEntries(os.DirFS(goModUIDir), \".\", localUIBuildDir, \"node_modules\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to copy ui files: %w\", err)\n\t}\n\n\tpluginsDir := filepath.Join(b.tmpDir, \"vendor/github.com/apache/answer-plugins/\")\n\tlocalUIPluginDir := filepath.Join(localUIBuildDir, \"src/plugins/\")\n\n\t// copy plugins dir\n\tfmt.Printf(\"try to copy dir from %s to %s\\n\", pluginsDir, localUIPluginDir)\n\n\t// if plugins dir not exist means no plugins\n\tif !dir.CheckDirExist(pluginsDir) {\n\t\treturn nil\n\t}\n\n\tpluginsDirEntries, err := os.ReadDir(pluginsDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read plugins dir: %w\", err)\n\t}\n\tfor _, entry := range pluginsDirEntries {\n\t\tif !entry.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tsourcePluginDir := filepath.Join(pluginsDir, entry.Name())\n\t\t// check if plugin is a ui plugin\n\t\tpackageJsonPath := filepath.Join(sourcePluginDir, \"package.json\")\n\t\tfmt.Printf(\"check if %s is a ui plugin\\n\", packageJsonPath)\n\t\tif !dir.CheckFileExist(packageJsonPath) {\n\t\t\tcontinue\n\t\t}\n\n\t\tpnpmInstallCmd := b.newExecCmd(\"pnpm\", \"install\")\n\t\tpnpmInstallCmd.Dir = sourcePluginDir\n\t\tif err = pnpmInstallCmd.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to install plugin dependencies: %w\", err)\n\t\t}","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/apache/answer/blob/3b9f1370612e690a0b7f230f05e688930db4c6d3/internal/cli/build.go#L333-L369","documentation":"After copying the UI files, copyUIFiles reads the vendored plugins directory (tmpDir/vendor/github.com/apache/answer-plugins/) with os.ReadDir to iterate UI plugins. This error wraps any os.ReadDir failure. It can only happen if CheckDirExist(pluginsDir) passed but the directory became unreadable between the check and the read (or permission bits deny listing).","triggerScenarios":"os.ReadDir(pluginsDir) fails with e.g. permission denied or, rarely, a TOCTOU race where the directory is removed between dir.CheckDirExist and os.ReadDir, or pluginsDir exists but is a file/symlink pointing somewhere unreadable.","commonSituations":"Plugins dir created by a previous build under a different user (root) with restrictive permissions; an antivirus/sync tool (Dropbox, OneDrive) locking or moving the dir mid-build; pluginsDir actually being a broken symlink or a regular file named like a directory.","solutions":["Check permissions on tmpDir/vendor/github.com/apache/answer-plugins/ (`ls -la`) and chown/chmod so the build user can read it (e.g. `chmod -R u+rwX`).","Delete the stale vendored plugins dir and re-run so movePluginToVendor recreates it.","Confirm the path is a real directory, not a symlink or file; remove or fix the symlink.","Re-run the build; if a sync/AV tool interferes, exclude the build/tmp directory from it."],"exampleFix":"// before: check-then-read race\nif !dir.CheckDirExist(pluginsDir) {\n\treturn nil\n}\npluginsDirEntries, err := os.ReadDir(pluginsDir)\n// after: single authoritative read\npluginsDirEntries, err := os.ReadDir(pluginsDir)\nif os.IsNotExist(err) {\n\treturn nil // no plugins\n}\nif err != nil {\n\treturn fmt.Errorf(\"failed to read plugins dir: %w\", err)\n}","handlingStrategy":"validation","validationCode":"pluginsDir := filepath.Join(b.tmpDir, \"vendor/github.com/apache/answer-plugins/\")\nif fi, err := os.Stat(pluginsDir); err == nil {\n\tif !fi.IsDir() { return fmt.Errorf(\"%s is not a directory\", pluginsDir) }\n\tif err := syscall.Access(pluginsDir, unix.R_OK|unix.X_OK); err != nil {\n\t\treturn fmt.Errorf(\"cannot list %s: %w\", pluginsDir, err)\n\t}\n}","typeGuard":"func isReadableDir(p string) bool {\n\tfi, err := os.Stat(p)\n\tif err != nil || !fi.IsDir() { return false }\n\tf, err := os.Open(p)\n\tif err != nil { return false }\n\tf.Close()\n\treturn true\n}","tryCatchPattern":"_, err := os.ReadDir(pluginsDir)\nif errors.Is(err, os.ErrNotExist) {\n\treturn nil // no plugins installed\n}\nif errors.Is(err, os.ErrPermission) {\n\treturn fmt.Errorf(\"fix permissions on %s: %w\", pluginsDir, err)\n}\nreturn err","preventionTips":["Run all build steps as the same user so previously vendored dirs stay readable.","Don't place the build tmp dir inside synced/locked folders (Dropbox, OneDrive, antivirus scan targets).","Prefer os.ReadDir's ErrNotExist over a separate exists-check to avoid TOCTOU races.","Verify vendored paths are directories, not symlinks or files, after movePluginToVendor."],"tags":["filesystem","permissions","build","plugins"],"backgroundTag":"directory-not-readable","analyzedSha":"3b9f1370612e690a0b7f230f05e688930db4c6d3","analyzedAt":"2026-09-05T18:18:39.533Z","contentChangedAt":"2026-09-05T18:18:39.533Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}