gastownhall/beads · error

failed to read config.yaml: %w

Error message

failed to read config.yaml: %w

What it means

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.

Source

Thrown at internal/config/repos.go:41

// FindConfigYAMLPath finds the config.yaml file in .beads directory
// Walks up from CWD to find .beads/config.yaml
func FindConfigYAMLPath() (string, error) {
	configPath, err := findProjectConfigYaml()
	if err != nil {
		return "", fmt.Errorf("no .beads/config.yaml found in current directory or parents")
	}
	return configPath, nil
}

// GetReposFromYAML reads the repos configuration from config.yaml
// Returns an empty ReposConfig if repos section doesn't exist
func GetReposFromYAML(configPath string) (*ReposConfig, error) {
	data, err := os.ReadFile(configPath) // #nosec G304 - config file path from caller
	if err != nil {
		if os.IsNotExist(err) {
			return &ReposConfig{}, nil
		}
		return nil, fmt.Errorf("failed to read config.yaml: %w", err)
	}

	// Parse into a generic map to extract repos section
	var cfg map[string]interface{}
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("failed to parse config.yaml: %w", err)
	}

	repos := &ReposConfig{}
	if reposRaw, ok := cfg["repos"]; ok && reposRaw != nil {
		reposMap, ok := reposRaw.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("repos section is not a map")
		}

		if primary, ok := reposMap["primary"].(string); ok {
			repos.Primary = primary
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions: ls -l .beads/config.yaml and chmod u+r (or chown) as needed.
  2. Confirm the path is a file, not a directory: file .beads/config.yaml.
  3. If permissions were broken by a sudo-run command, re-own the .beads tree: sudo chown -R $(whoami) .beads.

Example fix

// before: config owned by root
// after
$ sudo chown -R $(whoami) .beads
$ chmod 600 .beads/config.yaml
Defensive patterns

Strategy: validation

Validate before calling

fi, err := os.Stat(configPath)
if err != nil {
    return err
}
if fi.IsDir() {
    return fmt.Errorf("%s is a directory, expected a file", configPath)
}
f, err := os.OpenFile(configPath, os.O_RDONLY, 0)
if err != nil {
    return fmt.Errorf("cannot read config: %w", err)
}
f.Close()

Try / catch

repos, err := config.GetReposFromYAML(configPath)
if err != nil && strings.Contains(err.Error(), "failed to read config.yaml") {
    log.Printf("fix permissions on %s: %v", configPath, err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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