{"record":{"id":"8dd31ad50582ddcb","repo":"alibaba/open-code-review","slug":"read-app-config-s-w","errorCode":null,"errorMessage":"read app config %s: %w","messagePattern":"read app config (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/opencodereview/config_cmd.go","lineNumber":408,"sourceCode":"\t\t\treturn &Config{}, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\tvar cfg Config\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse config: %w\", err)\n\t}\n\treturn &cfg, nil\n}\n\n// LoadAppConfig loads config from path. Returns nil, nil if file does not exist.\nfunc LoadAppConfig(path string) (*Config, error) {\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"read app config %s: %w\", path, err)\n\t}\n\tvar cfg Config\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse app config: %w\", err)\n\t}\n\treturn &cfg, nil\n}\n\n// supportedConfigKeys is the single source of truth for the top-level config\n// keys accepted by setConfigValue. The unknown-key error message is generated\n// from this list so the two cannot drift apart when a new key is added.\nvar supportedConfigKeys = []string{\n\t\"provider\",\n\t\"model\",\n\t\"max_tokens\",\n\t\"effort\",\n\t\"providers.<name>.<field>\",\n\t\"custom_providers.<name>.<field>\",","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/cmd/opencodereview/config_cmd.go#L390-L426","documentation":"LoadAppConfig reads the config file for read-only consumers (e.g. loadLLMRuntime, runLLMTest). If os.ReadFile fails for any reason other than the file not existing (which yields nil, nil), the error is wrapped as 'read app config <path>: %w'. Unlike loadOrCreateConfig, a missing file is not an error.","triggerScenarios":"Calling LoadAppConfig (directly or via LLM runtime setup / 'ocr llm test') when the config path exists but cannot be read: permission denied, path is a directory, I/O error, or symlink to an unreadable target.","commonSituations":"Config file created with sudo and 0600 root ownership; OCR config path env/flag pointing at a directory; NFS/disk errors; file deleted between existence check and read in a race.","solutions":["Check the wrapped cause to distinguish permission vs path vs I/O problems.","Fix permissions: chown/chmod the config file for the current user.","Verify the path points to a regular file, not a directory.","If the config is optional, remember a missing file is fine (returns nil, nil) — only fix genuine read failures."],"exampleFix":"// before\n$ sudo ocr config set provider x  # config now root-owned\n$ ocr llm test\nread app config /home/u/.ocr/config.json: permission denied\n\n// after\n$ sudo chown -R $USER ~/.ocr && ocr llm test","handlingStrategy":"try-catch","validationCode":"info, err := os.Stat(path)\nif err == nil && info.IsDir() {\n    return fmt.Errorf(\"%s is a directory, expected a config file\", path)\n}\nif err == nil && info.Mode().Perm()&0o400 == 0 {\n    return fmt.Errorf(\"%s is not readable by current user\", path)\n}","typeGuard":null,"tryCatchPattern":"cfg, err := LoadAppConfig(path)\nif err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && errors.Is(perr.Err, fs.ErrPermission) {\n        fmt.Fprintf(os.Stderr, \"fix permissions on %s\\n\", path)\n    }\n    return err\n}","preventionTips":["Do not run config-writing commands under sudo on a user-owned config.","Point the config path flag/env at a regular file, not a directory.","Remember missing files are treated as empty config — only readable-but-failing files need fixing."],"tags":["go","cli","config","file-io"],"backgroundTag":"config-read-failed","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}