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

  1. Verify the endpoint returns the expected {"plugin": {...}} JSON schema
  2. Check that the server (registry/GitHub raw) is not returning an HTML error page
  3. Retry on transient network truncation
  4. 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

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


AI-assisted analysis of matryer/xbar@d624239058 (2026-09-02). Data as JSON: /api/errors/d6fc2ebd6f4d156d. Report an issue: GitHub.