matryer/xbar · error
error decoding plugin %s
Error message
error decoding plugin %s
What it means
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).
Source
Thrown at pkg/plugins/install.go:76
}
return installedPluginPath, nil
}
// fetchPlugin fetches the plugin metadata and file contents from the xbar website.
func (i Installer) fetchPlugin(pluginPath *url.URL) (metadata.Plugin, error) {
resp, err := i.Client.Get(pluginPath.String())
if err != nil {
return metadata.Plugin{}, err
}
if resp.StatusCode != http.StatusOK {
return metadata.Plugin{}, errors.Errorf("error fetching plugin %s: %s", pluginPath, resp.Status)
}
defer resp.Body.Close()
var responseBody struct {
Plugin metadata.Plugin `json:"plugin"`
}
if err := json.NewDecoder(resp.Body).Decode(&responseBody); err != nil {
return metadata.Plugin{}, errors.Wrapf(err, "error decoding plugin %s", pluginPath)
}
return responseBody.Plugin, nil
}
// getInstalledPluginName builds a name for the file or folder that will be
// created in the plugin installation directory. Since a plugin can be installed
// multiple times, each installed plugin will be given a sequence number to
// ensure unique naming in the filesystem.
func (i Installer) getInstalledPluginName(plugin metadata.Plugin) (string, error) {
var (
count int
candidatePath string
err error
)
for err == nil {
count++
candidateBaseName := fmt.Sprintf("%03d-%s", count, plugin.Filename)
candidatePath = filepath.Join(i.PluginDir, candidateBaseName)View on GitHub (pinned to d624239058)
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
Example fix
// before: hitting a raw HTML page
resp := client.Get("https://example.com/plugin-page.html")
// after: request the JSON API endpoint
resp := client.Get("https://xbarapp.com/api/v1/plugins/...json") Defensive patterns
Strategy: fallback
Validate before calling
// before decoding: sanity-check the body looks like JSON
body, _ := io.ReadAll(resp.Body)
trimmed := bytes.TrimSpace(body)
if len(trimmed) == 0 || (trimmed[0] != '{' && trimmed[0] != '[') {
return fmt.Errorf("unexpected non-JSON response from %s", pluginURL)
} Try / catch
// Go
plugin, err := installer.Install(ctx, pluginPath)
if err != nil {
if strings.Contains(err.Error(), "error decoding plugin") {
// fall back to raw download instead of metadata install
return fallbackDirectDownload(ctx, pluginPath)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- malformed xbar.var format (default not in select options)
- unknown xbar.var type: %s
- error fetching plugin %s: %s
- malformed parameters: missing equals
- expected an int, not "%s"
AI-assisted analysis of matryer/xbar@d624239058 (2026-09-02).
Data as JSON: /api/errors/d6fc2ebd6f4d156d.
Report an issue: GitHub.