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
- Validate the file: `jq . providers.json` to find the syntax error.
- Ensure the top level is a JSON array `[ {...provider...} ]`, not an object.
- Match catwalk.Provider field names/types exactly (snake_case JSON tags).
- 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
- Run `jq . file.json` as a pre-commit check on provider files.
- Export arrays of providers, never a single object, for UpdateProviders.
- Strip BOM and comments; encoding/json accepts neither.
- Pin the file format to catwalk.Provider's snake_case JSON schema.
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
- questions must be an array: %w
- invalid JSON in config file %s
- default providers are disabled and there are no custom provi
- no providers found in the provided source
- unmarshal response: %w: %s
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/34c728f8a970b156.
Report an issue: GitHub.