charmbracelet/crush · error

failed to create directory for provider cache: %w

Error message

failed to create directory for provider cache: %w

What it means

Thrown by cache.Store() when os.MkdirAll cannot create the parent directory of the cache path. The cache layer requires the directory to exist before writing the temp file, so a filesystem-level failure (permissions, read-only mount, path is a file) aborts the store.

Source

Thrown at internal/config/provider.go:287

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
	// truncating write would let one of them read a half-written catalog and
	// silently fall back to the bundled copy.
	if err := atomicWriteFile(c.path, data, 0o644); err != nil {
		return fmt.Errorf("failed to write provider data to cache: %w", err)
	}
	return nil
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check permissions on the ancestor directories of the cache path and fix with chmod/chown.
  2. If a file exists where a directory is expected, remove/rename that file.
  3. Point the cache location to a writable directory (fix HOME/XDG_CACHE_HOME or the config path option).
  4. Ensure the container/mount is writable (rw mount, writable $HOME).
  5. Free disk space if the failure is ENOSPC.

Example fix

// before
export XDG_CACHE_HOME=/mnt/readonly/cache
// after
export XDG_CACHE_HOME=$HOME/.cache
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(cachePath)
if info, err := os.Stat(dir); err == nil && !info.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", dir)
}
if err := os.MkdirAll(dir, 0o755); err != nil { return err }

Try / catch

if err := cache.Store(v); err != nil {
    if errors.Is(err, fs.ErrPermission) {
        slog.Warn("Cache dir unwritable; using in-memory providers")
    }
}

Prevention

When it happens

Trigger: Calling Store() (directly or via Init, fetch, UpdateProviders, UpdateHyper, etc.) where filepath.Dir(c.path) cannot be created: parent path component exists as a regular file, EACCES on the ancestor directory, or the filesystem is read-only (e.g. container with unwritable HOME).

Common situations: HOME or XDG_CACHE_HOME pointing to a read-only or non-existent mount; a file named like the cache directory blocks MkdirAll; running as a different user with restrictive umask; Docker images with non-writable $HOME.

Related errors


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