gastownhall/beads · error

read rules directory: %w

Error message

read rules directory: %w

What it means

RunAudit wraps os.ReadDir failures for the rules directory. A missing directory is deliberately NOT an error (returns empty AuditResult); this error fires only for other ReadDir failures such as permission denied or the path being a file.

Source

Thrown at cmd/bd/rules.go:578

	var sourceNames []string
	for _, r := range rules {
		sourceNames = append(sourceNames, r.Name+".md")
	}
	sb.WriteString("\nSource rules: ")
	sb.WriteString(strings.Join(sourceNames, ", "))
	sb.WriteString("\n")

	return sb.String(), nil
}

// RunAudit is the top-level orchestrator for `bd rules audit`.
func RunAudit(rulesDir string, threshold float64) (*AuditResult, error) {
	entries, err := os.ReadDir(rulesDir)
	if err != nil {
		if os.IsNotExist(err) {
			return &AuditResult{}, nil
		}
		return nil, fmt.Errorf("read rules directory: %w", err)
	}

	var rules []RuleFile
	totalTokens := 0

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		if !strings.HasSuffix(entry.Name(), ".md") {
			continue
		}

		path := filepath.Join(rulesDir, entry.Name())
		rf, err := ParseRuleFile(path)
		if err != nil {
			// Skip files that can't be parsed
			fmt.Fprintf(os.Stderr, "Warning: skipping %s: %v\n", entry.Name(), err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions: ls -ld <rulesDir>; ensure the running user has read+execute.
  2. Verify the path is a directory, not a file: test -d <rulesDir>.
  3. Fix ownership (chown/chmod) or point the rules dir config at a writable, readable location.
  4. If IsNotExist, no error is thrown — check that you are not confusing this with a different failure.

Example fix

// before
res, err := RunAudit(rulesDir, 0.5)
// after
if info, statErr := os.Stat(rulesDir); statErr == nil && !info.IsDir() {
    return fmt.Errorf("rules path %s is not a directory", rulesDir)
}
res, err := RunAudit(rulesDir, 0.5)
Defensive patterns

Strategy: validation

Validate before calling

if info, err := os.Stat(rulesDir); err != nil || !info.IsDir() {
    return fmt.Errorf("rules dir %s is not a readable directory", rulesDir)
}

Try / catch

res, err := RunAudit(rulesDir, threshold)
if err != nil {
    return fmt.Errorf("rules audit failed: %w", err) // check permissions first
}

Prevention

When it happens

Trigger: RunAudit called with a rulesDir that exists but is unreadable (permissions) or is a regular file rather than a directory.

Common situations: Rules directory with restrictive permissions (e.g. created by another user or root-only); config pointing rulesDir at a file; mounted volume with wrong ownership; containerized environments with dropped capabilities.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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