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

  1. Check the cache path permissions and free space (`df -h`, `ls -ld ~/.cache`).
  2. Set XDG_CACHE_HOME (or the equivalent) to a writable directory.
  3. Re-run after freeing disk space or fixing quota.
  4. 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

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


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