gastownhall/beads · error

read %s: %w

Error message

read %s: %w

What it means

ParseFile could not read the formula file from disk; os.ReadFile's error is wrapped as "read <path>: <reason>". This is the standard file-not-found / permission-denied family: the path resolved but the contents were unreadable.

Source

Thrown at internal/formula/parser.go:135

// ParseFile parses a formula from a file path.
// Detects format from extension: .formula.toml or .formula.json
func (p *Parser) ParseFile(path string) (*Formula, error) {
	// Check cache first
	absPath, err := filepath.Abs(path)
	if err != nil {
		return nil, fmt.Errorf("resolve path: %w", err)
	}

	if cached, ok := p.cache[absPath]; ok {
		return cached, nil
	}

	// Read and parse the file
	// #nosec G304 -- absPath comes from controlled search paths or explicit user input
	data, err := os.ReadFile(absPath)
	if err != nil {
		return nil, fmt.Errorf("read %s: %w", path, err)
	}

	// Detect format from extension
	var formula *Formula
	if strings.HasSuffix(path, FormulaExtTOML) {
		formula, err = p.ParseTOML(data)
	} else {
		formula, err = p.Parse(data)
	}
	if err != nil {
		return nil, fmt.Errorf("parse %s: %w", path, err)
	}

	formula.Source = absPath

	// Set source tracing info on all steps (gt-8tmz.18)
	SetSourceInfo(formula)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Confirm the file exists at the printed path (ls) and fix the path/casing if not.
  2. Check read permissions (chmod/chown) or re-clone the repository.
  3. If the formula should ship with the repo, commit it under .beads/formulas.
  4. Handle os.IsNotExist in callers to surface a friendly 'formula missing' message.

Example fix

// before
f, err := parser.ParseFile(".beads/formulas/scafolding.formula.toml") // not found
// after
f, err := parser.ParseFile(".beads/formulas/scaffolding.formula.toml") // correct name
Defensive patterns

Strategy: try-catch

Validate before calling

if fi, err := os.Stat(path); err != nil {
	return fmt.Errorf("formula file not available: %w", err)
} else if fi.IsDir() {
	return fmt.Errorf("%s is a directory, not a formula file", path)
}

Type guard

func readableFile(path string) bool {
	fi, err := os.Stat(path)
	return err == nil && !fi.IsDir()
}

Try / catch

f, err := parser.ParseFile(path)
if err != nil {
	var pe *fs.PathError
	if errors.As(err, &pe) && os.IsNotExist(pe) {
		return nil, fmt.Errorf("formula %s is not installed; commit it under .beads/formulas", path)
	}
	return nil, err
}

Prevention

When it happens

Trigger: Parser.ParseFile (via loadFormula) is given a path that does not exist, was deleted after discovery, lacks read permission, or is a directory.

Common situations: Formula file not committed/checked out; case-sensitive filesystem mismatch (Linux vs macOS filename casing); stale cache pointing at a removed temp file; file permissions after cloning as another user.

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/79be6c39d4423bcf. Report an issue: GitHub.