charmbracelet/crush · error

failed to create data directory: %q %w

Error message

failed to create data directory: %q %w

What it means

createDotCrushDir creates the workspace data directory (mode 0700) and this error wraps os.MkdirAll failure. The quoted %q shows exactly which directory path could not be created, alongside the underlying OS error. It aborts workspace creation because subsequent data (DB, config) cannot live without the directory.

Source

Thrown at internal/backend/util.go:11

package backend

import (
	"fmt"
	"os"
	"path/filepath"
)

func createDotCrushDir(dir string) error {
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return fmt.Errorf("failed to create data directory: %q %w", dir, err)
	}

	gitIgnorePath := filepath.Join(dir, ".gitignore")
	if _, err := os.Stat(gitIgnorePath); os.IsNotExist(err) {
		if err := os.WriteFile(gitIgnorePath, []byte("*\n"), 0o644); err != nil {
			return fmt.Errorf("failed to create .gitignore file: %q %w", gitIgnorePath, err)
		}
	}

	return nil
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the quoted path and underlying OS error to identify permission vs existence issues.
  2. If a regular file occupies the path, remove or rename it and retry.
  3. Check write permission on the parent directory (ls -ld on each component).
  4. Choose a different data directory on a writable filesystem.

Example fix

// before
// crash on read-only HOME
createDotCrushDir(filepath.Join(os.Getenv("HOME"), ".crush"))
// after
// pick a writable location explicitly
dir := os.Getenv("CRUSH_DATA_DIR")
if dir == "" {
    dir = filepath.Join(os.TempDir(), "crush-data")
}
err := createDotCrushDir(dir)
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Stat(dir); err == nil {
    if !fi.IsDir() {
        return fmt.Errorf("%s exists and is not a directory", dir)
    }
}
parent := filepath.Dir(dir)
if fi, err := os.Stat(parent); err != nil || !fi.IsDir() {
    return fmt.Errorf("parent %s missing", parent)
}
if err := unix.Access(parent, unix.W_OK); err != nil {
    return fmt.Errorf("parent %s not writable: %w", parent, err)
}

Type guard

func IsMkdirErr(err error) (string, bool) {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrExist) || errors.Is(err, fs.ErrPermission) {
        return pe.Path, true
    }
    return "", false
}

Try / catch

if err := createDotCrushDir(dir); err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrPermission) {
        // fall back to a writable data dir
        return createDotCrushDir(filepath.Join(os.TempDir(), "crush"))
    }
    return err
}

Prevention

When it happens

Trigger: Calling CreateWorkspace (indirectly via createDotCrushDir) when os.MkdirAll(dir, 0o700) fails — parent path doesn't exist and can't be created, permission denied on a parent, the path exists as a regular file, or the filesystem is read-only.

Common situations: Pointing the data dir at a read-only mount (containers, CI sandboxes); a file named .crush already exists where a directory is expected; running as a user without write access to the parent directory; HOME unset or mispointed.

Related errors


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