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
- Check file permissions on the config path (chmod u+r) and that it is a regular file, not a directory
- Confirm which path is failing from the %s in the message and inspect it with ls -la
- Fix or remove the broken path (e.g. delete a directory mistakenly named crush.json)
- 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
- Ship configs with 0644 permissions
- Point CRUSH_CONFIG at a file, never a directory
- Check mounts are up before starting services that read configs
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
- failed to persist docker mcp configuration: %w
- failed to check init flag file: %w
- failed to create parent directories: %w
- failed to create output file: %w
- failed to access file: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/a2a6ed683f44e3ef.
Report an issue: GitHub.