charmbracelet/crush · error
failed to save providers to cache: %w
Error message
failed to save providers to cache: %w
What it means
After successfully resolving providers, UpdateProviders persists them via the generic cache writer (newCache[[]catwalk.Provider](cachePathFor("providers")).Store). This error wraps any failure writing the cache: unwritable cache directory, disk full, permission denied, or an atomic-write/rename failure.
Source
Thrown at internal/config/provider.go:85
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 != "" {
return key
}
if cfg == nil || cfg.Providers == nil {
return ""
}
pc, ok := cfg.Providers.Get("hyper")
if !ok {
return ""View on GitHub (pinned to 7944b8e522)
Solutions
- Check the cache path permissions and free space (`df -h`, `ls -ld ~/.cache`).
- Set XDG_CACHE_HOME (or the equivalent) to a writable directory.
- Re-run after freeing disk space or fixing quota.
- Delete a corrupt/partial cache file and retry the update.
Example fix
// before XDG_CACHE_HOME=/mnt/ro crush update-providers // after export XDG_CACHE_HOME="$HOME/.cache" crush update-providers
Defensive patterns
Strategy: try-catch
Validate before calling
cacheDir := cachePathFor("providers")
if err := os.MkdirAll(filepath.Dir(cacheDir), 0o755); err != nil {
return fmt.Errorf("cache dir not writable: %w", err)
}
if err := unix.Access(filepath.Dir(cacheDir), unix.W_OK); err != nil {
return fmt.Errorf("no write permission on cache dir: %w", err)
} Try / catch
if err := config.UpdateProviders(src); err != nil {
var pe *fs.PathError
if errors.As(err, &pe) && errors.Is(pe.Err, syscall.ENOSPC) {
slog.Error("disk full while caching providers")
}
return err
} Prevention
- Ensure HOME/XDG_CACHE_HOME points to a writable location.
- Monitor disk space/quota in CI where updates run.
- Use the atomic-write safe path (do not bypass the cache Store helper).
- On Windows, exclude the cache dir from antivirus file locks.
When it happens
Trigger: Calling config.UpdateProviders when the OS cache directory (cachePathFor("providers")) is missing and cannot be created, is read-only, is on a full filesystem, or the atomic temp-file rename fails (e.g. Windows ERROR_ACCESS_DENIED).
Common situations: Read-only HOME or XDG_CACHE_HOME; disk quota exceeded; running in a sandboxed/containerized environment without a writable cache dir; antivirus locking the temp file on Windows.
Related errors
- failed to save Hyper provider to cache: %w
- docker MCP started but failed to persist configuration: %w
- failed to create server working directory: %v
- failed to persist docker mcp configuration: %w
- failed to update preferred large model: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/89e4f3bf7d62b60d.
Report an issue: GitHub.