alibaba/open-code-review · error

unsupported extension %q, only .md/.txt/.markdown allowed

Error message

unsupported extension %q, only .md/.txt/.markdown allowed

What it means

readRuleFileSafe only allows rule files with .md, .txt or .markdown extensions (case-insensitive). Any other extension is rejected to keep rule content predictable and prevent loading binaries or executable file types.

Source

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

	}
	return nil
}

// readRuleFileSafe reads and validates a rule file: extension whitelist, 512 KB cap,
// and symlink resolution. When confineRoot is non-empty, the resolved path must stay
// inside it. Returns the trimmed content on success.
func readRuleFileSafe(path string, confineRoot string) (string, error) {
	resolved, err := filepath.EvalSymlinks(path)
	if err != nil {
		return "", err
	}

	if confineRoot != "" && !pathutil.WithinBase(confineRoot, resolved) {
		return "", fmt.Errorf("rule file path %q escapes repo dir %q", resolved, confineRoot)
	}

	if !allowedRuleExts[strings.ToLower(filepath.Ext(resolved))] {
		return "", fmt.Errorf("unsupported extension %q, only .md/.txt/.markdown allowed", filepath.Ext(resolved))
	}

	const maxSize = 512 * 1024
	info, err := os.Stat(resolved)
	if err != nil {
		return "", err
	}
	if info.Size() > maxSize {
		return "", fmt.Errorf("file too large (%d bytes, max %d)", info.Size(), maxSize)
	}

	content, err := os.ReadFile(resolved)
	if err != nil {
		return "", err
	}

	return strings.TrimRight(string(content), "\n"), nil
}

View on GitHub (pinned to 5cf97d0d15)

Solutions

  1. Rename the rule file to end in .md, .txt, or .markdown
  2. Convert the content to Markdown/plain text if it is another format
  3. Check the --rule argument points at the intended prose file, not a config

Example fix

// before
ocr review --rule rules/review.yaml
// after
mv rules/review.yaml rules/review.md   # convert content to markdown
ocr review --rule rules/review.md
Defensive patterns

Strategy: validation

Validate before calling

func hasAllowedRuleExt(p string) bool {
    switch strings.ToLower(filepath.Ext(p)) {
    case ".md", ".txt", ".markdown":
        return true
    }
    return false
}
// check before invoking: if !hasAllowedRuleExt(flag) { convert/rename }

Try / catch

_, err := tryReadRuleFile(rulePath, confineRoot)
if err != nil && strings.Contains(err.Error(), "unsupported extension") {
    fmt.Fprintf(os.Stderr, "rename %s to .md, .txt or .markdown\n", rulePath)
    os.Exit(2)
}

Prevention

When it happens

Trigger: tryReadRuleFile receives a rule path ending in e.g. .json, .yaml, .rst, or no extension at all; allowedRuleExts lookup on the lowercased filepath.Ext fails.

Common situations: Passing a .yml config as a rule; referencing a README with .adoc; a rule file saved without any extension; confusion with the .opencodereview/rule.json (that file is loaded separately and not subject to this extension gate).

Related errors


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