charmbracelet/crush · error

failed to create .gitignore file: %q %w

Error message

failed to create .gitignore file: %q %w

What it means

setupLocalWorkspace in internal/cmd/root.go writes a `.gitignore` containing `*\n` into the data directory so crush's state is never committed. If os.WriteFile fails (permissions, read-only fs, path is a directory), it returns 'failed to create .gitignore file: %q %w'. The data dir already existed or was created, so this is a secondary but blocking failure.

Source

Thrown at internal/cmd/root.go:298

	}

	store, err := config.Init(cwd, dataDir, debug)
	if err != nil {
		return nil, nil, err
	}

	cfg := store.Config()
	store.Overrides().SkipPermissionRequests = yolo
	store.Overrides().EnabledChannels = channels

	if err := os.MkdirAll(cfg.Options.DataDirectory, 0o700); err != nil {
		return nil, nil, fmt.Errorf("failed to create data directory: %q %w", cfg.Options.DataDirectory, err)
	}

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

	if err := projects.Register(cwd, cfg.Options.DataDirectory); err != nil {
		slog.Warn("Failed to register project", "error", err)
	}

	conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
	if err != nil {
		return nil, nil, err
	}

	logFile := filepath.Join(cfg.Options.DataDirectory, "logs", "crush.log")
	crushlog.Setup(logFile, debug)

	// Discover skills once before app.New. Local mode hosts a single
	// workspace per process, so WithGlobalMirror keeps the package
	// globals (which the TUI reads via skills.GetLatestStates) in sync

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Fix ownership/permissions of the data directory: chown -R $(whoami) <dir> && chmod u+w <dir>
  2. If <dir>/.gitignore is a directory, remove it (rm -rf) and retry
  3. Manually create the file: echo '*' > <datadir>/.gitignore, then rerun
  4. Mount the data directory writable if in a container

Example fix

// shell, before
sudo crush run  # data dir now root-owned
// after
sudo chown -R $(whoami) ~/.local/share/crush
crush run
Defensive patterns

Strategy: validation

Validate before calling

if err := os.Chmod(dataDir, 0o700); err != nil {
    return fmt.Errorf("data dir %s not writable: %w", dataDir, err)
}
os.WriteFile(filepath.Join(dataDir, ".gitignore"), []byte("*\n"), 0o644)

Try / catch

if err := os.WriteFile(gitIgnorePath, []byte("*\n"), 0o644); err != nil {
    if errors.Is(err, fs.ErrPermission) {
        // chown/chmod the data dir or recreate it as the current user
    }
    return err
}

Prevention

When it happens

Trigger: The data directory exists but is not writable by the current user; `.gitignore` path is actually a directory; filesystem is read-only or full; restrictive umask/ACLs.

Common situations: Data directory created previously by another user (e.g. via sudo); syncing tools (Dropbox) locking files; container volumes mounted read-only after first run; SELinux/AppArmor denials.

Related errors


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