{"record":{"id":"79be6c39d4423bcf","repo":"gastownhall/beads","slug":"read-s-w","errorCode":null,"errorMessage":"read %s: %w","messagePattern":"read (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/parser.go","lineNumber":135,"sourceCode":"\n// ParseFile parses a formula from a file path.\n// Detects format from extension: .formula.toml or .formula.json\nfunc (p *Parser) ParseFile(path string) (*Formula, error) {\n\t// Check cache first\n\tabsPath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"resolve path: %w\", err)\n\t}\n\n\tif cached, ok := p.cache[absPath]; ok {\n\t\treturn cached, nil\n\t}\n\n\t// Read and parse the file\n\t// #nosec G304 -- absPath comes from controlled search paths or explicit user input\n\tdata, err := os.ReadFile(absPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read %s: %w\", path, err)\n\t}\n\n\t// Detect format from extension\n\tvar formula *Formula\n\tif strings.HasSuffix(path, FormulaExtTOML) {\n\t\tformula, err = p.ParseTOML(data)\n\t} else {\n\t\tformula, err = p.Parse(data)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parse %s: %w\", path, err)\n\t}\n\n\tformula.Source = absPath\n\n\t// Set source tracing info on all steps (gt-8tmz.18)\n\tSetSourceInfo(formula)\n","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/parser.go#L117-L153","documentation":"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.","triggerScenarios":"Parser.ParseFile (via loadFormula) is given a path that does not exist, was deleted after discovery, lacks read permission, or is a directory.","commonSituations":"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.","solutions":["Confirm the file exists at the printed path (ls) and fix the path/casing if not.","Check read permissions (chmod/chown) or re-clone the repository.","If the formula should ship with the repo, commit it under .beads/formulas.","Handle os.IsNotExist in callers to surface a friendly 'formula missing' message."],"exampleFix":"// before\nf, err := parser.ParseFile(\".beads/formulas/scafolding.formula.toml\") // not found\n// after\nf, err := parser.ParseFile(\".beads/formulas/scaffolding.formula.toml\") // correct name","handlingStrategy":"try-catch","validationCode":"if fi, err := os.Stat(path); err != nil {\n\treturn fmt.Errorf(\"formula file not available: %w\", err)\n} else if fi.IsDir() {\n\treturn fmt.Errorf(\"%s is a directory, not a formula file\", path)\n}","typeGuard":"func readableFile(path string) bool {\n\tfi, err := os.Stat(path)\n\treturn err == nil && !fi.IsDir()\n}","tryCatchPattern":"f, err := parser.ParseFile(path)\nif err != nil {\n\tvar pe *fs.PathError\n\tif errors.As(err, &pe) && os.IsNotExist(pe) {\n\t\treturn nil, fmt.Errorf(\"formula %s is not installed; commit it under .beads/formulas\", path)\n\t}\n\treturn nil, err\n}","preventionTips":["Commit all formula files; verify with git status after cloning.","Watch for filename casing differences across OS filesystems.","Check read permissions after cloning as a different user."],"tags":["go","filesystem","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}