{"record":{"id":"53636ceed127291f","repo":"alibaba/open-code-review","slug":"read-project-rule-s-w","errorCode":null,"errorMessage":"read project rule %s: %w","messagePattern":"read project rule (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/rules/system_rules.go","lineNumber":435,"sourceCode":"\tpath := filepath.Join(repoDir, \".opencodereview\", \"rule.json\")\n\tresolved, err := filepath.EvalSymlinks(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(\"resolve project rule %s: %w\", path, err)\n\t}\n\tif !pathutil.WithinBase(confineRoot, resolved) {\n\t\tfmt.Fprintf(os.Stderr, \"[ocr] WARNING: project rule file escapes repo dir: %s\\n\", path)\n\t\treturn nil, nil\n\t}\n\n\tdata, err := os.ReadFile(resolved)\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 project rule %s: %w\", path, err)\n\t}\n\tvar pr ProjectRule\n\tif err := json.Unmarshal(data, &pr); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal project rule: %w\", err)\n\t}\n\tresolveRuleEntries(pr.Rules, repoDir, confineRoot)\n\treturn &pr, nil\n}\n\n// Resolve checks each layer in priority order; first match wins. User rules\n// replace the system rule by default; rules with merge_system_rule keep the\n// matched system rule alongside the user rule.\nfunc (c *composedResolver) Resolve(path string) string {\n\tfor _, layer := range []*ProjectRule{c.custom, c.project, c.global} {\n\t\tif entry := matchProjectRuleEntry(layer, path); entry != nil {\n\t\t\tif entry.MergeSystemRule {\n\t\t\t\treturn c.mergeWithSystemRule(path, entry.Rule)\n\t\t\t}","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/config/rules/system_rules.go#L417-L453","documentation":"Once EvalSymlinks succeeds, the resolved path is read with os.ReadFile. Non-NotExist read failures (EACCES, EISDIR, I/O errors) are wrapped with the rule path. NotExist still returns nil,nil so a repo without a rule file works normally.","triggerScenarios":"os.ReadFile on the symlink-resolved rule.json fails with permission denied, or the path is actually a directory, or a device I/O error occurs.","commonSituations":"rule.json chmod 600 owned by another user; someone replaced rule.json with a directory of the same name; NFS/EFS mount glitches in CI containers.","solutions":["Fix file permissions: chmod a+r .opencodereview/rule.json or chown to the running user","Verify the path is a regular file (rm if it is a directory and recreate as a file)","Validate the JSON is readable: `cat .opencodereview/rule.json`","Check the wrapped os error for the exact errno"],"exampleFix":"// before\n-rw------- 1 root rule.json\n// after\nchmod 644 .opencodereview/rule.json","handlingStrategy":"validation","validationCode":"func readableRegularFile(p string) error {\n    fi, err := os.Stat(p)\n    if err != nil { return err }\n    if !fi.Mode().IsRegular() { return fmt.Errorf(\"%s is not a regular file\", p) }\n    f, err := os.Open(p)\n    if err != nil { return err }\n    return f.Close()\n}","typeGuard":null,"tryCatchPattern":"pr, err := loadProjectRule(repoDir)\nif err != nil && errors.Is(err, os.ErrPermission) {\n    fmt.Fprintln(os.Stderr, \"fix with: chmod a+r .opencodereview/rule.json\")\n    os.Exit(2)\n} else if err != nil { return err }","preventionTips":["Commit rule.json with 644 permissions","Never replace rule.json with a directory or symlink to a special file","Test `cat .opencodereview/rule.json` in CI before running ocr"],"tags":["go","filesystem","permissions","config"],"backgroundTag":"file-read-permission-denied","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}