charmbracelet/crush · error

failed to unmarshal provider data from cache: %w

Error message

failed to unmarshal provider data from cache: %w

What it means

Thrown by cache.Get() when the cache file exists but json.Unmarshal fails on its contents. Indicates the cached provider data is not valid JSON or does not match the expected schema (e.g. truncated or hand-edited file).

Source

Thrown at internal/config/provider.go:278

}

type cache[T any] struct {
	path string
}

func newCache[T any](path string) cache[T] {
	return cache[T]{path: path}
}

func (c cache[T]) Get() (T, string, error) {
	var v T
	data, err := os.ReadFile(c.path)
	if err != nil {
		return v, "", fmt.Errorf("failed to read provider cache file: %w", err)
	}

	if err := json.Unmarshal(data, &v); err != nil {
		return v, "", fmt.Errorf("failed to unmarshal provider data from cache: %w", err)
	}

	return v, etag.Of(data), nil
}

func (c cache[T]) Store(v T) error {
	slog.Info("Saving provider data to disk", "path", c.path)
	if err := os.MkdirAll(filepath.Dir(c.path), 0o755); err != nil {
		return fmt.Errorf("failed to create directory for provider cache: %w", err)
	}

	data, err := json.Marshal(v)
	if err != nil {
		return fmt.Errorf("failed to marshal provider data: %w", err)
	}

	// Written through a temporary file and renamed into place. Several Crush
	// instances start independently and race to refresh this cache, and a

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Delete the cache file and run `crush update-providers` to regenerate it.
  2. Verify the file is valid JSON (e.g. with `jq . <file>`).
  3. Upgrade/downgrade to a matching Crush version if schema drift is the cause.
  4. Use unmarshal options (DisallowUnknownFields off / custom decode) if calling the library directly with newer data.

Example fix

// before (corrupt cache)
crush run  // -> failed to unmarshal provider data from cache
// after
rm ~/.cache/crush/providers.json && crush update-providers
Defensive patterns

Strategy: validation

Validate before calling

var probe json.RawMessage
if err := json.Unmarshal(data, &probe); err != nil { regenerateCache() }

Type guard

func isValidProviderCache(path string) bool {
    data, err := os.ReadFile(path)
    if err != nil { return false }
    return json.Unmarshal(data, &map[string]any{}) == nil || json.Valid(data)
}

Try / catch

v, _, err := c.Get()
if err != nil {
    var unmarshalErr *json.UnmarshalTypeError
    if errors.As(err, &unmarshalErr) || strings.Contains(err.Error(), "unmarshal") {
        os.Remove(c.path); v = fetchFresh()
    }
}

Prevention

When it happens

Trigger: cache.Get() reads a file that was truncated mid-write (older non-atomic writer), hand-edited with a syntax error, or written by an incompatible Crush version with a changed schema.

Common situations: Crash during an old version's cache write; manual editing of the cache file; downgrade to a version expecting an older schema; disk corruption.

Related errors


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