matryer/xbar · error

ReadDir

Error message

ReadDir

What it means

Returned by GetInstalledPlugins when ioutil/os.ReadDir on the plugins directory fails with an error other than NotExist (which is treated as 'no plugins installed' and returns nil, nil). The raw directory-read error is wrapped with the label 'ReadDir'. It means the plugins directory exists but could not be listed.

Solutions

  1. Fix permissions on the plugin directory so the current user can read it (chmod a+rX / chown).
  2. Verify the PluginDir path: if an intermediate component is a file, remove or correct the configuration.
  3. If the directory should not exist, the error path was NotExist — check for a stale file at the path and delete it, then retry.

Example fix

// before
dir := "/var/lib/upterm/plugins" // root-owned
pls, err := plugins.GetInstalledPlugins(dir)
// after
if fi, err := os.Stat(dir); err == nil && fi.Mode().Perm()&0444 == 0 {
    os.Chmod(dir, 0755)
}
pls, err := plugins.GetInstalledPlugins(dir)
Defensive patterns

Strategy: try-catch

Validate before calling

func readableDir(dir string) error {
    fi, err := os.Stat(dir)
    if os.IsNotExist(err) { return nil } // treated as no plugins
    if err != nil { return err }
    if !fi.IsDir() { return fmt.Errorf("%s is not a directory", dir) }
    f, err := os.Open(dir)
    if err != nil { return err }
    f.Close()
    return nil
}

Try / catch

pls, err := plugins.GetInstalledPlugins(dir)
if err != nil && strings.Contains(err.Error(), "ReadDir") {
    var pe *os.PathError
    if errors.As(err, &pe) && os.IsPermission(pe) {
        log.Fatalf("cannot read plugin dir %s: fix permissions", pe.Path)
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetInstalledPlugins when the plugin directory exists but cannot be read: permission denied on the directory, path component is a file, or an I/O error on the filesystem.

Common situations: Plugin dir owned by another user after a sudo install; PluginDir env/config pointing at a path whose component is a regular file; corrupted or failing mount.

Related errors


AI-assisted analysis of matryer/xbar@d624239058 (2026-09-02). Data as JSON: /api/errors/596db7bef3b93370. Report an issue: GitHub.

Appendix: source

Thrown at pkg/plugins/installed_plugins.go:62

	if !enabled && IsPluginEnabled(installedPluginPath) {
		newFullPluginPath := fullPluginPath + disabledPluginExtension
		newInstalledPluginPath := installedPluginPath + disabledPluginExtension
		err := os.Rename(fullPluginPath, newFullPluginPath)
		return newInstalledPluginPath, err
	}
	return installedPluginPath, nil
}

// GetInstalledPlugins gets the installed fplugins from the pluginDirectory.
func GetInstalledPlugins(pluginDirectory string) ([]InstalledPlugin, error) {
	files, err := ioutil.ReadDir(pluginDirectory)
	if err != nil {
		if os.IsNotExist(err) {
			// no plugins directory - so no installed plugins
			// but not an error
			return nil, nil
		}
		return nil, errors.Wrap(err, "ReadDir")
	}
	var installedPlugins []InstalledPlugin
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		if strings.HasPrefix(file.Name(), ".") {
			continue
		}
		if strings.HasSuffix(file.Name(), variableJSONFileExt) {
			// ignore variable payload files
			continue
		}
		enabled := !strings.HasSuffix(file.Name(), disabledPluginExtension)
		name := file.Name()
		installedPlugin := InstalledPlugin{
			Name:    name,
			Path:    file.Name(),

View on GitHub (pinned to d624239058)