charmbracelet/crush · error
failed to read provider cache file: %w
Error message
failed to read provider cache file: %w
What it means
Thrown by cache.Get() when os.ReadFile cannot read the provider cache file at c.path. The cache layer treats a missing/corrupt cache as fatal for that read, so callers (e.g. resolveHyperAPIKey) receive the wrapped OS error. Typically means the file does not exist yet or is unreadable.
Source
Thrown at internal/config/provider.go:274
}
}
// Provider not found in list; prepend it.
providerList = append([]catwalk.Provider{provider}, providerList...)
}
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)View on GitHub (pinned to 7944b8e522)
Solutions
- Ensure the cache file exists by triggering a refresh: run `crush update-providers` (and `crush update-hyper` if applicable).
- Check file permissions on the cache path (and parent dirs) so the current user can read it.
- If the cache is corrupt/undesired, delete it and let Crush recreate it.
- Verify env vars controlling the cache directory (e.g. XDG_CACHE_HOME) point to a valid, writable location.
- Wrap the call to tolerate os.IsNotExist and fall back to fetching the catalog.
Example fix
// before
v, _, err := cache.Get()
// after
v, _, err := cache.Get()
if errors.Is(err, fs.ErrNotExist) {
err = cache.Store(fresh) // or fetch from network
} Defensive patterns
Strategy: fallback
Validate before calling
if _, err := os.Stat(cachePath); errors.Is(err, fs.ErrNotExist) {
// skip cache read; fetch fresh data
} Type guard
func cacheReadable(path string) bool {
f, err := os.Open(path)
if err != nil { return false }
f.Close()
return true
} Try / catch
v, etag, err := c.Get()
if err != nil {
if errors.Is(err, fs.ErrNotExist) { v = fetchFresh() } else { return err }
} Prevention
- Always Store() before relying on Get() results
- Don't delete cache files under an active Crush install
- Verify XDG/HOME env vars point to writable, stable locations
- Treat missing cache as cold-start, not a hard failure
When it happens
Trigger: Calling cache.Get() (directly or via resolveHyperAPIKey / the syncer's anonymous getter) before the cache file was ever written; the file was deleted; wrong permissions; c.path points to a nonexistent directory.
Common situations: First run before any cache store; user or cleanup tool removed ~/.cache/crush files; permission restrictions after switching users; cache path misconfigured via env (XDG dirs).
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- failed to create server working directory: %v
- failed to read file: %w
- failed to save providers to cache: %w
- failed to save Hyper provider to cache: %w
- failed to create directory for provider cache: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/62986233c73e71aa.
Report an issue: GitHub.