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

  1. 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`).
  2. Delete the stale vendored plugins dir and re-run so movePluginToVendor recreates it.
  3. Confirm the path is a real directory, not a symlink or file; remove or fix the symlink.
  4. 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

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


AI-assisted analysis of apache/answer@3b9f137061 (2026-09-05). Data as JSON: /api/errors/3682e2235b952e15. Report an issue: GitHub.