hashicorp/nomad · error · ErrPluginNotExists

%w: %q (wraps ErrPluginNotExists)

Error message

%w: %q (wraps ErrPluginNotExists)

What it means

`NewExternalSecretsPlugin` in the Nomad client's commonplugins package looks for a secrets plugin binary under commonPluginDir/SecretsPluginDir, appending .exe on Windows. If os.Stat reports the file does not exist, it returns ErrPluginNotExists wrapped with the plugin binary name. Nomad uses errors.Is/As against ErrPluginNotExists to distinguish "plugin absent" from real failures.

Source

Thrown at client/commonplugins/secrets_plugin.go:59

	logger log.Logger

	// pluginPath is the path on the host to the plugin executable
	pluginPath string
}

// NewExternalSecretsPlugin creates an instance of a secrets plugin by validating the plugin
// binary exists and is executable, and parsing any string key/value pairs out of the config
// which will be used as environment variables for Fetch.
func NewExternalSecretsPlugin(commonPluginDir string, name string) (*externalSecretsPlugin, error) {
	// validate plugin
	if runtime.GOOS == "windows" {
		name += ".exe"
	}
	executable := filepath.Join(commonPluginDir, SecretsPluginDir, name)
	f, err := os.Stat(executable)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, fmt.Errorf("%w: %q", ErrPluginNotExists, name)
		}
		return nil, err
	}
	if !helper.IsExecutable(f) {
		return nil, fmt.Errorf("%w: %q", ErrPluginNotExecutable, name)
	}

	return &externalSecretsPlugin{pluginPath: executable}, nil
}

func (e *externalSecretsPlugin) Fingerprint(ctx context.Context) (*PluginFingerprint, error) {
	plugCtx, cancel := context.WithTimeout(ctx, SecretsCmdTimeout)
	defer cancel()

	cmd := exec.CommandContext(plugCtx, e.pluginPath, "fingerprint")
	cmd.Env = []string{
		"CPI_OPERATION=fingerprint",
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Install the secrets plugin binary into <plugin_dir>/<SecretsPluginDir>/ with the expected name
  2. Verify the plugin name/path in your agent config matches the actual file on disk
  3. If the plugin is optional, handle errors.Is(err, commonplugins.ErrPluginNotExists) by skipping instead of failing
  4. Confirm correct binary name for the OS (no .exe suffix needed on Linux, required on Windows)

Example fix

// handling code
// before
p, err := commonplugins.NewExternalSecretsPlugin(logger, name, dir)
if err != nil { return err }
// after
p, err := commonplugins.NewExternalSecretsPlugin(logger, name, dir)
if errors.Is(err, commonplugins.ErrPluginNotExists) {
    logger.Warn("secrets plugin not installed, skipping", "plugin", name)
    return nil
} else if err != nil {
    return err
}
Defensive patterns

Strategy: type-guard

Validate before calling

p := filepath.Join(pluginDir, "secrets", pluginName)
if _, err := os.Stat(p); os.IsNotExist(err) {
    log.Fatalf("secrets plugin %q not installed at %s", pluginName, p)
}

Type guard

func isPluginNotExists(err error) bool {
    return errors.Is(err, commonplugins.ErrPluginNotExists)
}

Try / catch

p, err := commonplugins.NewExternalSecretsPlugin(logger, name, dir)
switch {
case errors.Is(err, commonplugins.ErrPluginNotExists):
    return nil // plugin optional; skip
case err != nil:
    return err
}

Prevention

When it happens

Trigger: Fingerprinting or building secret providers when the expected secrets plugin executable is not installed in the plugin directory (e.g. plugins/secrets/ dir under the configured plugin_dir) or the plugin name is misspelled.

Common situations: Fresh Nomad install where the secrets plugin was never deployed; plugin deployed to the wrong directory; plugin binary name doesn't match the expected name; operators referencing a plugin only available on some nodes.

Related errors


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