charmbracelet/crush · error

failed to unmarshal provider data: %w

Error message

failed to unmarshal provider data: %w

What it means

After successfully reading a local provider file, UpdateProviders unmarshals it with encoding/json into []catwalk.Provider. This error wraps the json.Unmarshal failure: the file is not valid JSON or its shape does not match []catwalk.Provider (e.g. an object instead of an array, wrong field types).

Source

Thrown at internal/config/provider.go:77

	var providers []catwalk.Provider
	pathOrURL = cmp.Or(pathOrURL, os.Getenv("CATWALK_URL"), defaultCatwalkURL)

	switch {
	case pathOrURL == "embedded":
		providers = embedded.GetAll()
	case strings.HasPrefix(pathOrURL, "http://") || strings.HasPrefix(pathOrURL, "https://"):
		var err error
		providers, err = catwalk.NewWithURL(pathOrURL).GetProviders(context.Background(), "")
		if err != nil {
			return fmt.Errorf("failed to fetch providers from Catwalk: %w", err)
		}
	default:
		content, err := os.ReadFile(pathOrURL)
		if err != nil {
			return fmt.Errorf("failed to read file: %w", err)
		}
		if err := json.Unmarshal(content, &providers); err != nil {
			return fmt.Errorf("failed to unmarshal provider data: %w", err)
		}
		if len(providers) == 0 {
			return fmt.Errorf("no providers found in the provided source")
		}
	}

	if err := newCache[[]catwalk.Provider](cachePathFor("providers")).Store(providers); err != nil {
		return fmt.Errorf("failed to save providers to cache: %w", err)
	}

	slog.Info("Providers updated successfully", "count", len(providers), "from", pathOrURL, "to", cachePathFor)
	return nil
}

// resolveHyperAPIKey returns the Hyper API key from the environment or
// the raw config value. The env var takes precedence.
func resolveHyperAPIKey(cfg *Config) string {
	if key := os.Getenv("HYPER_API_KEY"); key != "" {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Validate the file: `jq . providers.json` to find the syntax error.
  2. Ensure the top level is a JSON array `[ {...provider...} ]`, not an object.
  3. Match catwalk.Provider field names/types exactly (snake_case JSON tags).
  4. Regenerate the file from a known-good source (embedded catalog or catwalk URL).

Example fix

// before (providers.json)
{"id": "openai", "name": "OpenAI"}
// after
[{"id": "openai", "name": "OpenAI"}]
Defensive patterns

Strategy: validation

Validate before calling

var probe []catwalk.Provider
content, err := os.ReadFile(path)
if err != nil { return err }
if err := json.Unmarshal(content, &probe); err != nil {
    return fmt.Errorf("provider file is not valid JSON for []catwalk.Provider: %w", err)
}

Try / catch

var synErr *json.SyntaxError
if err := json.Unmarshal(content, &providers); err != nil {
    if errors.As(err, &synErr) {
        slog.Error("bad JSON", "offset", synErr.Offset, "msg", synErr.Error())
    }
    return err
}

Prevention

When it happens

Trigger: Calling config.UpdateProviders with a file path to content that is not a JSON array of providers: truncated JSON, JSONL, a single provider object, comments in JSON, or wrong field types (numbers where strings expected).

Common situations: Exporting a single provider instead of an array; hand-editing JSON and leaving a trailing comma; BOM or comments at the top of the file; schema drift after a catwalk.Provider field type change.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/34c728f8a970b156. Report an issue: GitHub.