{"record":{"id":"b917b63572327d3e","repo":"charmbracelet/crush","slug":"read-config-file-w","errorCode":null,"errorMessage":"read config file: %w","messagePattern":"read config file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/store.go","lineNumber":316,"sourceCode":"// new contents. fn must be pure — no I/O, no network calls.\nfunc (s *ConfigStore) atomicWrite(scope Scope, fn func(current []byte) ([]byte, error)) error {\n\tunlock, err := s.lockConfig(scope)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unlock()\n\n\tpath, err := s.configPath(scope)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\tdata = []byte(\"{}\")\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"read config file: %w\", err)\n\t\t}\n\t}\n\n\tnewData, err := fn(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn atomicWriteFile(path, newData, 0o600)\n}\n\n// configPath returns the file path for the given scope.\nfunc (s *ConfigStore) configPath(scope Scope) (string, error) {\n\tswitch scope {\n\tcase ScopeWorkspace:\n\t\tif s.workspacePath == \"\" {\n\t\t\treturn \"\", ErrNoWorkspaceConfig\n\t\t}","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/store.go#L298-L334","documentation":"atomicWrite reads the current config file before applying the transform callback. Missing files are treated as empty config ({}), but any other read error — permission denied, path is a directory, I/O error — is wrapped as this error and aborts the write.","triggerScenarios":"Calling writeConfigFields or RemoveConfigField when the config file exists but cannot be read: the file's mode denies read access, the path is actually a directory, or a low-level I/O error occurs. (Not triggered when the file is simply absent.)","commonSituations":"Config file created by another user/root with restrictive modes; a directory accidentally created at the config file's path; corrupted filesystem; running under a different user via sudo so ownership no longer matches.","solutions":["Fix the file's permissions so the current user can read it (chmod u+r)","Check whether the config path is a directory (ls -la) and remove/rename it","Verify ownership matches the running user (chown)","Restore the config file from backup or delete it so it is recreated as {}"],"exampleFix":"// before\n// file exists but unreadable:\nos.WriteFile(cfgPath, data, 0o000)\nstore.RemoveConfigField(scope, key) // fails\n// after\nif info, err := os.Stat(cfgPath); err == nil && info.Mode().Perm()&0o400 == 0 {\n    os.Chmod(cfgPath, 0o600) // ensure owner-readable before writing\n}\nstore.RemoveConfigField(scope, key)","handlingStrategy":"validation","validationCode":"info, err := os.Stat(configPath)\nif err == nil {\n    if info.IsDir() {\n        return fmt.Errorf(\"%s is a directory, expected a file\", configPath)\n    }\n    if f, err := os.Open(configPath); err != nil {\n        return fmt.Errorf(\"config file unreadable: %w\", err)\n    } else { f.Close() }\n}","typeGuard":"func configReadable(path string) bool {\n    f, err := os.Open(path)\n    if err != nil { return false }\n    f.Close()\n    return true\n}","tryCatchPattern":"var perr *fs.PathError\nif errors.As(err, &perr) && errors.Is(perr, fs.ErrPermission) {\n    os.Chmod(perr.Path, 0o600)\n    // retry the operation once\n}","preventionTips":["Keep config file ownership aligned with the running user (avoid sudo-created files)","Do not create directories at paths that should be config files","Restore or delete unreadable/corrupted config files so {} regeneration kicks in","Check mode bits (0o600) when provisioning config files via scripts"],"tags":["filesystem","permissions","config-file"],"backgroundTag":"config-file-unreadable","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}