{"record":{"id":"a2a6ed683f44e3ef","repo":"charmbracelet/crush","slug":"failed-to-open-config-file-s-w","errorCode":null,"errorMessage":"failed to open config file %s: %w","messagePattern":"failed to open config file (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/load.go","lineNumber":986,"sourceCode":"\tvar configs [][]byte\n\tvar loaded []string\n\n\t// Track directories that have both crush.json and crushrc to warn\n\t// about potential confusion, along with the top-level keys each\n\t// defines so we can report conflicts.\n\tjsonDirKeys := make(map[string]map[string]bool)\n\tshDirKeys := make(map[string]map[string]bool)\n\n\tfor _, path := range configPaths {\n\t\tif path == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to open config file %s: %w\", path, err)\n\t\t}\n\t\tif len(data) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tdir := filepath.Dir(path)\n\t\tif isShellConfig(path) {\n\t\t\tjsonBytes, err := shellconfig.LoadShellConfig(ctx, path, data)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"failed to load shell config %s: %w\", path, err)\n\t\t\t}\n\t\t\tif len(jsonBytes) > 0 {\n\t\t\t\tif !json.Valid(jsonBytes) {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\"shell config %s produced invalid JSON\", path)\n\t\t\t\t}\n\t\t\t\taddTopLevelKeys(shDirKeys, dir, jsonBytes)\n\t\t\t\tconfigs = append(configs, jsonBytes)\n\t\t\t\tloaded = append(loaded, path)","sourceCodeStart":968,"sourceCodeEnd":1004,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/load.go#L968-L1004","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n-rw------- root crush.json   # unreadable as normal user\n# after\nchmod 644 ~/.config/crush/crush.json","handlingStrategy":"validation","validationCode":"info, err := os.Stat(path)\nif err != nil { return err }\nif info.IsDir() { return fmt.Errorf(\"%s is a directory, expected a file\", path) }\nif f, err := os.Open(path); err != nil {\n  if errors.Is(err, os.ErrPermission) { return fmt.Errorf(\"%s not readable: fix permissions\", path) }\n  f.Close()\n}","typeGuard":null,"tryCatchPattern":"if _, err := config.Load(ctx, ...); err != nil {\n  if strings.Contains(err.Error(), \"failed to open config file\") {\n    // fall back to defaults or abort with a clear message\n  }\n}","preventionTips":["Ship configs with 0644 permissions","Point CRUSH_CONFIG at a file, never a directory","Check mounts are up before starting services that read configs"],"tags":["config","filesystem","permissions"],"backgroundTag":"config-file-unreadable","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}