hashicorp/nomad · error

failed to scan plugin directory %q: %v

Error message

failed to scan plugin directory %q: %v

What it means

After initializing internal plugins, the loader calls scan() to walk l.pluginDir for eligible plugin binaries. If that filesystem scan fails (unreadable directory, permission denied, stat errors), init wraps the error with the plugin directory path in this message.

Source

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

	return mErr.ErrorOrNil()
}

// init initializes the plugin loader by compiling both internal and external
// plugins and selecting the highest versioned version of any given plugin.
func (l *PluginLoader) init(cfg *PluginLoaderConfig) (map[string]*config.PluginConfig, error) {
	// Create a mapping of name to config
	configMap := configMap(cfg.Configs)

	// Initialize the internal plugins
	internal, err := l.initInternal(cfg.InternalPlugins, configMap)
	if err != nil {
		return nil, fmt.Errorf("failed to fingerprint internal plugins: %v", err)
	}

	// Scan for eligibile binaries
	plugins, err := l.scan()
	if err != nil {
		return nil, fmt.Errorf("failed to scan plugin directory %q: %v", l.pluginDir, err)
	}

	// Fingerprint the passed plugins
	external, err := l.fingerprintPlugins(plugins, configMap)
	if err != nil {
		return nil, fmt.Errorf("failed to fingerprint plugins: %v", err)
	}

	// Merge external and internal plugins
	l.plugins = l.mergePlugins(internal, external)

	// Validate that the configs are valid for the plugins
	canonicalizedConfigs, err := l.validatePluginConfigs()
	if err != nil {
		return nil, fmt.Errorf("parsing plugin configurations failed: %v", err)
	}

	for i := range configMap {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Check that the directory in the error message exists: ls <dir>.
  2. Fix permissions: chmod/chown the directory so the process user can read and traverse it.
  3. Correct the PluginDir value in the loader config.
  4. In containers, ensure the plugins volume is mounted and the path is created in the image.

Example fix

// before
cfg := &PluginLoaderConfig{PluginDir: "/opt/nomad-plugins-missing", ...}

// after
if _, err := os.Stat(pluginDir); err != nil {
    return fmt.Errorf("plugin dir unavailable: %w", err)
}
cfg := &PluginLoaderConfig{PluginDir: pluginDir, ...}
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(cfg.PluginDir)
if err != nil || !info.IsDir() {
    return fmt.Errorf("plugin dir %q missing or not a directory", cfg.PluginDir)
}
if err := unix.Access(cfg.PluginDir, unix.R_OK|unix.X_OK); err != nil {
    return fmt.Errorf("plugin dir %q not readable: %w", cfg.PluginDir, err)
}

Try / catch

loader, err := NewPluginLoader(cfg)
if err != nil {
    if strings.Contains(err.Error(), "failed to scan plugin directory") {
        return fmt.Errorf("check plugin dir existence/permissions: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: PluginDir points to a nonexistent path; the process lacks read/execute permission on the directory; the path is a file, not a directory; an FS error occurs while walking entries.

Common situations: Container images where the plugin dir wasn't created or mounted; running the app as a non-root user after plugins were installed as root with restrictive modes; typo'd path in config; bind mount missing in Kubernetes/Docker.

Related errors


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