apache/answer · error
failed to read plugins dir: %w
Error message
failed to read plugins dir: %w
What it means
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).
Source
Thrown at internal/cli/build.go:351
// The node_modules folder generated during development will interfere packaging, so it needs to be ignored.
if err = copyDirEntries(os.DirFS(goModUIDir), ".", localUIBuildDir, "node_modules"); err != nil {
return fmt.Errorf("failed to copy ui files: %w", err)
}
pluginsDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer-plugins/")
localUIPluginDir := filepath.Join(localUIBuildDir, "src/plugins/")
// copy plugins dir
fmt.Printf("try to copy dir from %s to %s\n", pluginsDir, localUIPluginDir)
// if plugins dir not exist means no plugins
if !dir.CheckDirExist(pluginsDir) {
return nil
}
pluginsDirEntries, err := os.ReadDir(pluginsDir)
if err != nil {
return fmt.Errorf("failed to read plugins dir: %w", err)
}
for _, entry := range pluginsDirEntries {
if !entry.IsDir() {
continue
}
sourcePluginDir := filepath.Join(pluginsDir, entry.Name())
// check if plugin is a ui plugin
packageJsonPath := filepath.Join(sourcePluginDir, "package.json")
fmt.Printf("check if %s is a ui plugin\n", packageJsonPath)
if !dir.CheckFileExist(packageJsonPath) {
continue
}
pnpmInstallCmd := b.newExecCmd("pnpm", "install")
pnpmInstallCmd.Dir = sourcePluginDir
if err = pnpmInstallCmd.Run(); err != nil {
return fmt.Errorf("failed to install plugin dependencies: %w", err)
}View on GitHub (pinned to 3b9f137061)
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.
Example fix
// before: check-then-read race
if !dir.CheckDirExist(pluginsDir) {
return nil
}
pluginsDirEntries, err := os.ReadDir(pluginsDir)
// after: single authoritative read
pluginsDirEntries, err := os.ReadDir(pluginsDir)
if os.IsNotExist(err) {
return nil // no plugins
}
if err != nil {
return fmt.Errorf("failed to read plugins dir: %w", err)
} Defensive patterns
Strategy: validation
Validate before calling
pluginsDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer-plugins/")
if fi, err := os.Stat(pluginsDir); err == nil {
if !fi.IsDir() { return fmt.Errorf("%s is not a directory", pluginsDir) }
if err := syscall.Access(pluginsDir, unix.R_OK|unix.X_OK); err != nil {
return fmt.Errorf("cannot list %s: %w", pluginsDir, err)
}
} Type guard
func isReadableDir(p string) bool {
fi, err := os.Stat(p)
if err != nil || !fi.IsDir() { return false }
f, err := os.Open(p)
if err != nil { return false }
f.Close()
return true
} Try / catch
_, err := os.ReadDir(pluginsDir)
if errors.Is(err, os.ErrNotExist) {
return nil // no plugins installed
}
if errors.Is(err, os.ErrPermission) {
return fmt.Errorf("fix permissions on %s: %w", pluginsDir, err)
}
return err Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to create directory %s: %w
- failed to create destination file %s: %w
- failed to copy ui files: %w
- failed to install plugin dependencies: %w
- failed to open source file %s: %w
AI-assisted analysis of apache/answer@3b9f137061 (2026-09-05).
Data as JSON: /api/errors/3682e2235b952e15.
Report an issue: GitHub.