gastownhall/beads · error

read rule file %s: %w

Error message

read rule file %s: %w

What it means

ParseRuleFile wraps any os.ReadFile failure with the path of the .md rule file it attempted to read. It is thrown when the file cannot be opened or read (missing file, permission denied, path is a directory). The %w wrap preserves the underlying os error for errors.Is/As inspection.

Source

Thrown at cmd/bd/rules.go:104

	"suppress": {"log"},
}

// --- Regex Patterns ---

var (
	headingRe = regexp.MustCompile(`(?m)^#\s+(.+)`)
	doRe      = regexp.MustCompile(`(?i)^\*\*Do:?\*\*:?\s*(.*)`)
	dontRe    = regexp.MustCompile(`(?i)^\*\*Don'?t:?\*\*:?\s*(.*)`)
)

// --- Core Functions ---

// ParseRuleFile reads a .md file and extracts structured rule data.
func ParseRuleFile(path string) (RuleFile, error) {
	// #nosec G304 -- path comes from controlled filepath.Join of user-specified rules directory
	data, err := os.ReadFile(path)
	if err != nil {
		return RuleFile{}, fmt.Errorf("read rule file %s: %w", path, err)
	}

	content := string(data)
	name := strings.TrimSuffix(filepath.Base(path), ".md")

	rf := RuleFile{
		Path: path,
		Name: name,
		Body: content,
		// Rough token estimate: 1 token ~ 4 chars
		Tokens: len(content) / 4,
	}

	// Extract title from first heading
	if m := headingRe.FindStringSubmatch(content); len(m) > 1 {
		rf.Title = strings.TrimSpace(m[1])
	} else {
		rf.Title = name

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify the file exists and is readable: ls -l <path> (or stat <path>).
  2. Check that the rules directory configured for bd contains the expected .md files.
  3. Fix filesystem permissions on the rules directory/file.
  4. If the path should be optional, handle the wrapped os.IsNotExist(err) before treating it as fatal.

Example fix

// before
rf, err := ParseRuleFile(maybePath)
if err != nil { return err }
// after
if _, statErr := os.Stat(maybePath); os.IsNotExist(statErr) {
    return nil // skip missing rule file
}
rf, err := ParseRuleFile(maybePath)
if err != nil { return err }
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(path); err != nil {
    // skip or report before calling ParseRuleFile
}

Try / catch

rf, err := ParseRuleFile(path)
if err != nil {
    if errors.Is(err, os.ErrNotExist) { /* handle missing file */ }
    return fmt.Errorf("rules audit: %w", err)
}

Prevention

When it happens

Trigger: Calling ParseRuleFile (directly or via RunAudit/runRulesCompact) with a path that does not exist, is unreadable, or points to a directory instead of a .md file.

Common situations: Misconfigured rules directory passed to `bd rules audit`/`bd rules compact`; a rules file was renamed or deleted; symlink pointing at a missing target; filesystem permission issues; passing a directory path instead of a file.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/16e432500e6d59fd. Report an issue: GitHub.