{"record":{"id":"89e4f3bf7d62b60d","repo":"charmbracelet/crush","slug":"failed-to-save-providers-to-cache-w","errorCode":null,"errorMessage":"failed to save providers to cache: %w","messagePattern":"failed to save providers to cache: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/provider.go","lineNumber":85,"sourceCode":"\t\tproviders, err = catwalk.NewWithURL(pathOrURL).GetProviders(context.Background(), \"\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to fetch providers from Catwalk: %w\", err)\n\t\t}\n\tdefault:\n\t\tcontent, err := os.ReadFile(pathOrURL)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read file: %w\", err)\n\t\t}\n\t\tif err := json.Unmarshal(content, &providers); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal provider data: %w\", err)\n\t\t}\n\t\tif len(providers) == 0 {\n\t\t\treturn fmt.Errorf(\"no providers found in the provided source\")\n\t\t}\n\t}\n\n\tif err := newCache[[]catwalk.Provider](cachePathFor(\"providers\")).Store(providers); err != nil {\n\t\treturn fmt.Errorf(\"failed to save providers to cache: %w\", err)\n\t}\n\n\tslog.Info(\"Providers updated successfully\", \"count\", len(providers), \"from\", pathOrURL, \"to\", cachePathFor)\n\treturn nil\n}\n\n// resolveHyperAPIKey returns the Hyper API key from the environment or\n// the raw config value. The env var takes precedence.\nfunc resolveHyperAPIKey(cfg *Config) string {\n\tif key := os.Getenv(\"HYPER_API_KEY\"); key != \"\" {\n\t\treturn key\n\t}\n\tif cfg == nil || cfg.Providers == nil {\n\t\treturn \"\"\n\t}\n\tpc, ok := cfg.Providers.Get(\"hyper\")\n\tif !ok {\n\t\treturn \"\"","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/provider.go#L67-L103","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nXDG_CACHE_HOME=/mnt/ro crush update-providers\n// after\nexport XDG_CACHE_HOME=\"$HOME/.cache\"\ncrush update-providers","handlingStrategy":"try-catch","validationCode":"cacheDir := cachePathFor(\"providers\")\nif err := os.MkdirAll(filepath.Dir(cacheDir), 0o755); err != nil {\n    return fmt.Errorf(\"cache dir not writable: %w\", err)\n}\nif err := unix.Access(filepath.Dir(cacheDir), unix.W_OK); err != nil {\n    return fmt.Errorf(\"no write permission on cache dir: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := config.UpdateProviders(src); err != nil {\n    var pe *fs.PathError\n    if errors.As(err, &pe) && errors.Is(pe.Err, syscall.ENOSPC) {\n        slog.Error(\"disk full while caching providers\")\n    }\n    return err\n}","preventionTips":["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."],"tags":["filesystem","cache","disk","persistence"],"backgroundTag":"cache-write-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}