hashicorp/nomad · error · ErrPluginNotExecutable
%w: %q (wraps ErrPluginNotExecutable)
Error message
%w: %q (wraps ErrPluginNotExecutable)
What it means
After confirming the secrets plugin binary exists, `NewExternalSecretsPlugin` checks it is executable via helper.IsExecutable(f) and returns ErrPluginNotExecutable wrapped with the plugin name when it is not. Nomad refuses to launch plugins that lack the executable bit (or equivalent on Windows), so plugin registration fails fast with a typed, detectable error.
Source
Thrown at client/commonplugins/secrets_plugin.go:64
// 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",
}
stdout, stderr, err := runPlugin(cmd, SecretsKillTimeout)
if err != nil {
return nil, err
}View on GitHub (pinned to 482b49bf1a)
Solutions
- chmod +x the plugin binary in the plugin directory
- Ensure deployment tooling preserves file modes (e.g. `unzip`, `tar -p`, Docker COPY of a pre-built executable)
- Verify the user Nomad runs as has execute permission on the file
- Optionally skip optional plugins by checking errors.Is(err, commonplugins.ErrPluginNotExecutable)
Example fix
// host fix // before: -rw-r--r-- plugins/secrets/secrets-plugin chmod 0755 /opt/nomad/plugins/secrets/secrets-plugin // after: -rwxr-xr-x plugins/secrets/secrets-plugin
Defensive patterns
Strategy: type-guard
Validate before calling
info, err := os.Stat(pluginPath)
if err != nil {
log.Fatal(err)
}
if info.Mode()&0o111 == 0 {
log.Fatalf("plugin %s is not executable: chmod +x %s", pluginPath, pluginPath)
} Type guard
func isPluginNotExecutable(err error) bool {
return errors.Is(err, commonplugins.ErrPluginNotExecutable)
} Try / catch
p, err := commonplugins.NewExternalSecretsPlugin(logger, name, dir)
switch {
case errors.Is(err, commonplugins.ErrPluginNotExecutable):
return fmt.Errorf("fix permissions on %s (chmod +x)", name)
case err != nil:
return err
} Prevention
- chmod 0755 plugin binaries after deployment
- Use deployment tooling that preserves file modes (tar -p, unzip -X, correct Docker COPY)
- Verify plugin permissions in post-deploy health checks
- Ensure the Nomad service user can execute the plugin directory tree
When it happens
Trigger: Deploying the secrets plugin binary without +x permissions, e.g. extracting an archive that did not preserve modes, or copying via a tool that resets permissions; running Nomad as a user without execute permission on the file.
Common situations: Ansible/Docker COPY of plugin binaries losing the executable bit; binaries mounted from volumes with noexec or restrictive perms; CI artifacts uploaded without mode preservation then deployed to Nomad plugin dirs.
Related errors
- plugin not executable
- failed executing plugin %q for secret %q: %w
- provider %q for secret %q response contained error: %q
- %w: %q (wraps ErrPluginNotExists)
- namespace cannot contain template delimiters or parenthesis
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/e47e1741c35c49f2.
Report an issue: GitHub.