docker/cli · error

unable to determine basename of plugin candidate

Error message

unable to determine basename of plugin candidate %q

What it means

Returned by newPlugin when filepath.Base(path) yields ".", meaning the candidate path has no meaningful basename. The plugin manager treats this as a non-recoverable error because a plugin cannot be identified without a filename. Normally the candidate listing skips such paths, so hitting it indicates an unexpected path shape.

Solutions

  1. Verify the plugin directory contains real executable files, not '.' or directory entries.
  2. Remove or fix the offending symlink/path that collapses to '.'.
  3. Check the candidate path string for trailing separators or empty segments.
Defensive patterns

Strategy: validation

Validate before calling

if filepath.Base(path) == "." {
    return fmt.Errorf("plugin path %q has no basename; skip", path)
}

Try / catch

p, err := newPlugin(candidate, cmds)
if err != nil {
    // skip this degenerate candidate
    continue
}

Prevention

When it happens

Trigger: Passing a pluginCandidate whose Path() resolves to a directory-only form like "." or ends in a separator so Base returns ".".

Common situations: A misconfigured plugin directory entry, a symlink resolving to the current directory, or a bug in candidate enumeration producing a degenerate path.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/a94505d0835cae82. Report an issue: GitHub.

Appendix: source

Thrown at cli-plugins/manager/plugin.go:71

	Metadata() ([]byte, error)
}

// newPlugin determines if the given candidate is valid and returns a
// Plugin.  If the candidate fails one of the tests then `Plugin.Err`
// is set, and is always a `pluginError`, but the `Plugin` is still
// returned with no error. An error is only returned due to a
// 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

View on GitHub (pinned to 4f84911bfe)