hashicorp/nomad · error

failed to open plugin directory %q: %v

Error message

failed to open plugin directory %q: %v

What it means

During scan, the loader opens the configured plugin_dir to enumerate external plugin files. If os.Open fails with an error other than not-exist (which is only logged and skipped), this error wraps it — typically a permissions problem or the path being a non-directory.

Source

Thrown at helper/pluginutils/loader/init.go:238

	return converted, nil
}

// scan scans the plugin directory and retrieves potentially eligible binaries
func (l *PluginLoader) scan() ([]os.FileInfo, error) {
	if l.pluginDir == "" {
		return nil, nil
	}

	// Capture the list of binaries in the plugins folder
	f, err := os.Open(l.pluginDir)
	if err != nil {
		// There are no plugins to scan
		if os.IsNotExist(err) {
			l.logger.Warn("skipping external plugins since plugin_dir doesn't exist")
			return nil, nil
		}

		return nil, fmt.Errorf("failed to open plugin directory %q: %v", l.pluginDir, err)
	}
	files, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return nil, fmt.Errorf("failed to read plugin directory %q: %v", l.pluginDir, err)
	}

	var plugins []os.FileInfo
	for _, f := range files {
		f = filepath.Join(l.pluginDir, f)
		s, err := os.Stat(f)
		if err != nil {
			return nil, fmt.Errorf("failed to stat file %q: %v", f, err)
		}
		if s.IsDir() {
			l.logger.Warn("skipping subdir in plugin folder", "subdir", f)
			continue
		}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Fix permissions so the Nomad agent user can read the plugin_dir (chmod/chown or run the agent with sufficient privileges)
  2. Verify plugin_dir in the agent config points to an existing directory, not a file
  3. Create the directory if it was deleted (os.MkdirAll) — note a missing dir is tolerated, but an unreadable existing path is not

Example fix

# before (agent config)
plugin_dir = "/etc/nomad-plugins.txt"
# after
plugin_dir = "/etc/nomad-plugins"
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(cfg.PluginDir)
if err != nil {
    return fmt.Errorf("plugin_dir %q: %w", cfg.PluginDir, err)
}
if !info.IsDir() {
    return fmt.Errorf("plugin_dir %q is not a directory", cfg.PluginDir)
}
if f, err := os.Open(cfg.PluginDir); err != nil {
    return fmt.Errorf("plugin_dir %q not readable: %w", cfg.PluginDir, err)
} else { f.Close() }

Try / catch

if err := loader.Init(...); err != nil {
    if strings.Contains(err.Error(), "failed to open plugin directory") {
        // check permissions/ownership of plugin_dir and that it's a directory
    }
}

Prevention

When it happens

Trigger: init -> scan with l.pluginDir pointing to a path that exists but cannot be opened: bad permissions, path is a file not a directory, mount unavailable, or an OS-level I/O error (anything other than fs.ErrNotExist).

Common situations: plugin_dir set in the Nomad agent config to a path owned by another user; docker/k8s volume not mounted; plugin_dir pointed at a file instead of a directory.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/21ff8410f7d2fd66. Report an issue: GitHub.