{"record":{"id":"d6fc2ebd6f4d156d","repo":"matryer/xbar","slug":"error-decoding-plugin-s","errorCode":null,"errorMessage":"error decoding plugin %s","messagePattern":"error decoding plugin (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/plugins/install.go","lineNumber":76,"sourceCode":"\t}\n\treturn installedPluginPath, nil\n}\n\n// fetchPlugin fetches the plugin metadata and file contents from the xbar website.\nfunc (i Installer) fetchPlugin(pluginPath *url.URL) (metadata.Plugin, error) {\n\tresp, err := i.Client.Get(pluginPath.String())\n\tif err != nil {\n\t\treturn metadata.Plugin{}, err\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn metadata.Plugin{}, errors.Errorf(\"error fetching plugin %s: %s\", pluginPath, resp.Status)\n\t}\n\tdefer resp.Body.Close()\n\tvar responseBody struct {\n\t\tPlugin metadata.Plugin `json:\"plugin\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&responseBody); err != nil {\n\t\treturn metadata.Plugin{}, errors.Wrapf(err, \"error decoding plugin %s\", pluginPath)\n\t}\n\treturn responseBody.Plugin, nil\n}\n\n// getInstalledPluginName builds a name for the file or folder that will be\n// created in the plugin installation directory. Since a plugin can be installed\n// multiple times, each installed plugin will be given a sequence number to\n// ensure unique naming in the filesystem.\nfunc (i Installer) getInstalledPluginName(plugin metadata.Plugin) (string, error) {\n\tvar (\n\t\tcount         int\n\t\tcandidatePath string\n\t\terr           error\n\t)\n\tfor err == nil {\n\t\tcount++\n\t\tcandidateBaseName := fmt.Sprintf(\"%03d-%s\", count, plugin.Filename)\n\t\tcandidatePath = filepath.Join(i.PluginDir, candidateBaseName)","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/matryer/xbar/blob/d624239058997c80118eaebe2e7f8331b3c765e0/pkg/plugins/install.go#L58-L94","documentation":"After a successful fetch, fetchPlugin decodes the response body into a struct containing metadata.Plugin. If the body is not valid JSON or does not match the expected shape, the decode error is wrapped with errors.Wrapf(err, \"error decoding plugin %s\", pluginPath).","triggerScenarios":"Calling Install against a URL whose response body is not the expected `{\"plugin\": {...}}` JSON — e.g. an HTML error page, truncated response, or a JSON with wrong field types.","commonSituations":"Reverse proxy or captive portal returning HTML; CDN/rate-limit error bodies with 200-ish handling upstream; server API version change renaming the `plugin` field; network truncation.","solutions":["Verify the endpoint returns the expected {\"plugin\": {...}} JSON schema","Check that the server (registry/GitHub raw) is not returning an HTML error page","Retry on transient network truncation","Pin/align the client with the registry's current API version"],"exampleFix":"// before: hitting a raw HTML page\nresp := client.Get(\"https://example.com/plugin-page.html\")\n// after: request the JSON API endpoint\nresp := client.Get(\"https://xbarapp.com/api/v1/plugins/...json\")","handlingStrategy":"fallback","validationCode":"// before decoding: sanity-check the body looks like JSON\nbody, _ := io.ReadAll(resp.Body)\ntrimmed := bytes.TrimSpace(body)\nif len(trimmed) == 0 || (trimmed[0] != '{' && trimmed[0] != '[') {\n    return fmt.Errorf(\"unexpected non-JSON response from %s\", pluginURL)\n}","typeGuard":null,"tryCatchPattern":"// Go\nplugin, err := installer.Install(ctx, pluginPath)\nif err != nil {\n    if strings.Contains(err.Error(), \"error decoding plugin\") {\n        // fall back to raw download instead of metadata install\n        return fallbackDirectDownload(ctx, pluginPath)\n    }\n    return err\n}","preventionTips":["Hit the JSON API endpoint, not the human HTML page","Check Content-Type is application/json before decoding","Guard against proxies/captive portals returning HTML","Log the response body on decode failure for diagnosis"],"tags":["json","network","parsing","plugins"],"backgroundTag":"json-decode-failed","analyzedSha":"d624239058997c80118eaebe2e7f8331b3c765e0","analyzedAt":"2026-09-02T22:38:22.007Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}