docker/cli · error
plugin candidate : does not have prefix
Error message
plugin candidate %q: does not have %q prefix
What it means
Returned by newPlugin when the trimmed filename does not start with the docker- prefix (metadata.NamePrefix). Docker CLI plugins must be named docker-<name> so the manager can derive the subcommand name by stripping the prefix. A binary without this prefix is rejected outright.
Solutions
- Rename the plugin executable to docker-<name>, e.g. docker-compose.
- Symlink the bare name to a docker- prefixed path if the original cannot be renamed.
- Remove non-prefixed binaries from the plugin search directories.
Example fix
# before mv bin/compose ~/.docker/cli-plugins/compose # after mv bin/compose ~/.docker/cli-plugins/docker-compose
Defensive patterns
Strategy: validation
Validate before calling
name := filepath.Base(path)
if !strings.HasPrefix(name, "docker-") {
return fmt.Errorf("plugin %q must be named docker-<name>", name)
} Try / catch
p, err := newPlugin(candidate, cmds)
if err != nil {
continue
} Prevention
- Name all plugin executables with the docker- prefix.
- Symlink bare names to docker- prefixed paths if renaming is not possible.
When it happens
Trigger: Placing an executable named 'compose' (instead of 'docker-compose') in the plugin search PATH, or any non-prefixed binary encountered as a candidate.
Common situations: Installing a plugin under its bare name, or renaming a plugin binary and dropping the docker- prefix.
Related errors
- unable to determine basename of plugin candidate
- context name cannot be empty
- "default" is a reserved context name
- hook template contains too many messages
- docker: unknown command: docker
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/760f6e07051350de.
Report an issue: GitHub.
Appendix: source
Thrown at cli-plugins/manager/plugin.go:78
// non-recoverable error.
func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
path := c.Path()
if path == "" {
return Plugin{}, errors.New("plugin candidate path cannot be empty")
}
// The candidate listing process should have skipped anything
// which would fail here, so there are all real errors.
fullname := filepath.Base(path)
if fullname == "." {
return Plugin{}, fmt.Errorf("unable to determine basename of plugin candidate %q", path)
}
var err error
if fullname, err = trimExeSuffix(fullname); err != nil {
return Plugin{}, fmt.Errorf("plugin candidate %q: %w", path, err)
}
if !strings.HasPrefix(fullname, metadata.NamePrefix) {
return Plugin{}, fmt.Errorf("plugin candidate %q: does not have %q prefix", path, metadata.NamePrefix)
}
p := Plugin{
Name: strings.TrimPrefix(fullname, metadata.NamePrefix),
Path: path,
}
// Now apply the candidate tests, so these update p.Err.
if !isValidPluginName(p.Name) {
p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameFormat)
return p, nil
}
for _, cmd := range cmds {
// Ignore conflicts with commands which are
// just plugin stubs (i.e. from a previous
// call to AddPluginCommandStubs).
if IsPluginCommand(cmd) {View on GitHub (pinned to 4f84911bfe)