{"record":{"id":"a94505d0835cae82","repo":"docker/cli","slug":"unable-to-determine-basename-of-plugin-candidate","errorCode":null,"errorMessage":"unable to determine basename of plugin candidate %q","messagePattern":"unable to determine basename of plugin candidate %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli-plugins/manager/plugin.go","lineNumber":71,"sourceCode":"\tMetadata() ([]byte, error)\n}\n\n// newPlugin determines if the given candidate is valid and returns a\n// Plugin.  If the candidate fails one of the tests then `Plugin.Err`\n// is set, and is always a `pluginError`, but the `Plugin` is still\n// returned with no error. An error is only returned due to a\n// non-recoverable error.\nfunc newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {\n\tpath := c.Path()\n\tif path == \"\" {\n\t\treturn Plugin{}, errors.New(\"plugin candidate path cannot be empty\")\n\t}\n\n\t// The candidate listing process should have skipped anything\n\t// which would fail here, so there are all real errors.\n\tfullname := filepath.Base(path)\n\tif fullname == \".\" {\n\t\treturn Plugin{}, fmt.Errorf(\"unable to determine basename of plugin candidate %q\", path)\n\t}\n\tvar err error\n\tif fullname, err = trimExeSuffix(fullname); err != nil {\n\t\treturn Plugin{}, fmt.Errorf(\"plugin candidate %q: %w\", path, err)\n\t}\n\tif !strings.HasPrefix(fullname, metadata.NamePrefix) {\n\t\treturn Plugin{}, fmt.Errorf(\"plugin candidate %q: does not have %q prefix\", path, metadata.NamePrefix)\n\t}\n\n\tp := Plugin{\n\t\tName: strings.TrimPrefix(fullname, metadata.NamePrefix),\n\t\tPath: path,\n\t}\n\n\t// Now apply the candidate tests, so these update p.Err.\n\tif !isValidPluginName(p.Name) {\n\t\tp.Err = newPluginError(\"plugin candidate %q did not match %q\", p.Name, pluginNameFormat)\n\t\treturn p, nil","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli-plugins/manager/plugin.go#L53-L89","documentation":"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.","triggerScenarios":"Passing a pluginCandidate whose Path() resolves to a directory-only form like \".\" or ends in a separator so Base returns \".\".","commonSituations":"A misconfigured plugin directory entry, a symlink resolving to the current directory, or a bug in candidate enumeration producing a degenerate path.","solutions":["Verify the plugin directory contains real executable files, not '.' or directory entries.","Remove or fix the offending symlink/path that collapses to '.'.","Check the candidate path string for trailing separators or empty segments."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if filepath.Base(path) == \".\" {\n    return fmt.Errorf(\"plugin path %q has no basename; skip\", path)\n}","typeGuard":null,"tryCatchPattern":"p, err := newPlugin(candidate, cmds)\nif err != nil {\n    // skip this degenerate candidate\n    continue\n}","preventionTips":["Keep the plugin directory free of '.'-like or directory-only entries.","Validate candidate paths have a real filename component."],"tags":["cli-plugins","plugins","filesystem","validation"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}