alibaba/open-code-review · error

read project rule %s: %w

Error message

read project rule %s: %w

What it means

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.

Source

Thrown at internal/config/rules/system_rules.go:435

	path := filepath.Join(repoDir, ".opencodereview", "rule.json")
	resolved, err := filepath.EvalSymlinks(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("resolve project rule %s: %w", path, err)
	}
	if !pathutil.WithinBase(confineRoot, resolved) {
		fmt.Fprintf(os.Stderr, "[ocr] WARNING: project rule file escapes repo dir: %s\n", path)
		return nil, nil
	}

	data, err := os.ReadFile(resolved)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("read project rule %s: %w", path, err)
	}
	var pr ProjectRule
	if err := json.Unmarshal(data, &pr); err != nil {
		return nil, fmt.Errorf("unmarshal project rule: %w", err)
	}
	resolveRuleEntries(pr.Rules, repoDir, confineRoot)
	return &pr, nil
}

// Resolve checks each layer in priority order; first match wins. User rules
// replace the system rule by default; rules with merge_system_rule keep the
// matched system rule alongside the user rule.
func (c *composedResolver) Resolve(path string) string {
	for _, layer := range []*ProjectRule{c.custom, c.project, c.global} {
		if entry := matchProjectRuleEntry(layer, path); entry != nil {
			if entry.MergeSystemRule {
				return c.mergeWithSystemRule(path, entry.Rule)
			}

View on GitHub (pinned to 5cf97d0d15)

Solutions

  1. Fix file permissions: chmod a+r .opencodereview/rule.json or chown to the running user
  2. Verify the path is a regular file (rm if it is a directory and recreate as a file)
  3. Validate the JSON is readable: `cat .opencodereview/rule.json`
  4. Check the wrapped os error for the exact errno

Example fix

// before
-rw------- 1 root rule.json
// after
chmod 644 .opencodereview/rule.json
Defensive patterns

Strategy: validation

Validate before calling

func readableRegularFile(p string) error {
    fi, err := os.Stat(p)
    if err != nil { return err }
    if !fi.Mode().IsRegular() { return fmt.Errorf("%s is not a regular file", p) }
    f, err := os.Open(p)
    if err != nil { return err }
    return f.Close()
}

Try / catch

pr, err := loadProjectRule(repoDir)
if err != nil && errors.Is(err, os.ErrPermission) {
    fmt.Fprintln(os.Stderr, "fix with: chmod a+r .opencodereview/rule.json")
    os.Exit(2)
} else if err != nil { return err }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02). Data as JSON: /api/errors/53636ceed127291f. Report an issue: GitHub.