charmbracelet/crush · error

failed to open config file %s: %w

Error message

failed to open config file %s: %w

What it means

loadFromConfigPaths reads each candidate config path with os.ReadFile. Missing files are skipped via os.IsNotExist, but any other read failure (permissions, is-a-directory, I/O error) aborts loading with this wrapped error including the path.

Source

Thrown at internal/config/load.go:986

	var configs [][]byte
	var loaded []string

	// Track directories that have both crush.json and crushrc to warn
	// about potential confusion, along with the top-level keys each
	// defines so we can report conflicts.
	jsonDirKeys := make(map[string]map[string]bool)
	shDirKeys := make(map[string]map[string]bool)

	for _, path := range configPaths {
		if path == "" {
			continue
		}
		data, err := os.ReadFile(path)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
			return nil, nil, fmt.Errorf("failed to open config file %s: %w", path, err)
		}
		if len(data) == 0 {
			continue
		}

		dir := filepath.Dir(path)
		if isShellConfig(path) {
			jsonBytes, err := shellconfig.LoadShellConfig(ctx, path, data)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to load shell config %s: %w", path, err)
			}
			if len(jsonBytes) > 0 {
				if !json.Valid(jsonBytes) {
					return nil, nil, fmt.Errorf("shell config %s produced invalid JSON", path)
				}
				addTopLevelKeys(shDirKeys, dir, jsonBytes)
				configs = append(configs, jsonBytes)
				loaded = append(loaded, path)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check file permissions on the config path (chmod u+r) and that it is a regular file, not a directory
  2. Confirm which path is failing from the %s in the message and inspect it with ls -la
  3. Fix or remove the broken path (e.g. delete a directory mistakenly named crush.json)
  4. If under a mount, verify the filesystem is accessible before starting

Example fix

# before
-rw------- root crush.json   # unreadable as normal user
# after
chmod 644 ~/.config/crush/crush.json
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(path)
if err != nil { return err }
if info.IsDir() { return fmt.Errorf("%s is a directory, expected a file", path) }
if f, err := os.Open(path); err != nil {
  if errors.Is(err, os.ErrPermission) { return fmt.Errorf("%s not readable: fix permissions", path) }
  f.Close()
}

Try / catch

if _, err := config.Load(ctx, ...); err != nil {
  if strings.Contains(err.Error(), "failed to open config file") {
    // fall back to defaults or abort with a clear message
  }
}

Prevention

When it happens

Trigger: Load (or benchmarks of loadFromConfigPaths) encounters a config path where os.ReadFile fails with a non-Not-Exist error: permission denied, path is a directory, too many symlinks, or an I/O error.

Common situations: crush.json or crushrc created with root-only permissions; CRUSH_CONFIG env var or config path pointing at a directory instead of a file; NFS/network mount hiccup; a directory named crush.json accidentally created.

Related errors


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