{"record":{"id":"023b2aa5fb884229","repo":"gastownhall/beads","slug":"failed-to-read-config-yaml-w","errorCode":null,"errorMessage":"failed to read config.yaml: %w","messagePattern":"failed to read config\\.yaml: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/repos.go","lineNumber":41,"sourceCode":"// FindConfigYAMLPath finds the config.yaml file in .beads directory\n// Walks up from CWD to find .beads/config.yaml\nfunc FindConfigYAMLPath() (string, error) {\n\tconfigPath, err := findProjectConfigYaml()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"no .beads/config.yaml found in current directory or parents\")\n\t}\n\treturn configPath, nil\n}\n\n// GetReposFromYAML reads the repos configuration from config.yaml\n// Returns an empty ReposConfig if repos section doesn't exist\nfunc GetReposFromYAML(configPath string) (*ReposConfig, error) {\n\tdata, err := os.ReadFile(configPath) // #nosec G304 - config file path from caller\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn &ReposConfig{}, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to read config.yaml: %w\", err)\n\t}\n\n\t// Parse into a generic map to extract repos section\n\tvar cfg map[string]interface{}\n\tif err := yaml.Unmarshal(data, &cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse config.yaml: %w\", err)\n\t}\n\n\trepos := &ReposConfig{}\n\tif reposRaw, ok := cfg[\"repos\"]; ok && reposRaw != nil {\n\t\treposMap, ok := reposRaw.(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"repos section is not a map\")\n\t\t}\n\n\t\tif primary, ok := reposMap[\"primary\"].(string); ok {\n\t\t\trepos.Primary = primary\n\t\t}","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/config/repos.go#L23-L59","documentation":"GetReposFromYAML reads config.yaml and wraps any read error other than NotExist (which is tolerated as an empty config) in this error. It indicates the config file exists but could not be read by the process.","triggerScenarios":"Calling GetReposFromYAML(configPath) (directly or via AddRepo/RemoveRepo/ListRepos) when os.ReadFile fails with e.g. EACCES (no read permission), EISDIR (configPath points at a directory), or I/O errors on the underlying storage.","commonSituations":"config.yaml owned by root or another user after a sudo-run init; .beads mounted with restrictive permissions; configPath mistakenly pointing to a directory; disk/network filesystem errors.","solutions":["Check permissions: ls -l .beads/config.yaml and chmod u+r (or chown) as needed.","Confirm the path is a file, not a directory: file .beads/config.yaml.","If permissions were broken by a sudo-run command, re-own the .beads tree: sudo chown -R $(whoami) .beads."],"exampleFix":"// before: config owned by root\n// after\n$ sudo chown -R $(whoami) .beads\n$ chmod 600 .beads/config.yaml","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(configPath)\nif err != nil {\n    return err\n}\nif fi.IsDir() {\n    return fmt.Errorf(\"%s is a directory, expected a file\", configPath)\n}\nf, err := os.OpenFile(configPath, os.O_RDONLY, 0)\nif err != nil {\n    return fmt.Errorf(\"cannot read config: %w\", err)\n}\nf.Close()","typeGuard":null,"tryCatchPattern":"repos, err := config.GetReposFromYAML(configPath)\nif err != nil && strings.Contains(err.Error(), \"failed to read config.yaml\") {\n    log.Printf(\"fix permissions on %s: %v\", configPath, err)\n}","preventionTips":["Avoid sudo-running bd commands that create .beads files.","Keep config.yaml mode 0600 owned by your user.","Pass a file path, never a directory, as configPath."],"tags":["config","filesystem","permissions","io"],"backgroundTag":"file-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}