gastownhall/beads · error
error merging config file %s: %w
Error message
error merging config file %s: %w
What it means
Initialize wraps errors from viper's MergeInConfig when merging each additional config file after the first. It means one of the overlay config paths exists but could not be parsed or read; the failing path is interpolated into the message. The viper cause is wrapped with %w.
Source
Thrown at internal/config/config.go:368
// 0 = hide title, N > 0 = truncate to N chars with "…"
v.SetDefault("output.title-length", 255)
// External projects for cross-project dependency resolution (bd-h807)
// Maps project names to paths for resolving external: blocked_by references
v.SetDefault("external_projects", map[string]string{})
// Load config files: lowest priority first, each MergeInConfig overwrites
if len(configPaths) > 0 {
v.SetConfigFile(configPaths[0])
if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("error reading config file: %w", err)
}
debug.Logf("Debug: loaded config from %s\n", configPaths[0])
for _, p := range configPaths[1:] {
v.SetConfigFile(p)
if err := v.MergeInConfig(); err != nil {
return fmt.Errorf("error merging config file %s: %w", p, err)
}
debug.Logf("Debug: merged config from %s\n", p)
}
// Restore primary config path as ConfigFileUsed (used by SaveConfigValue,
// ResolveExternalProjectPath, etc.)
v.SetConfigFile(primaryConfigPath)
// Merge local config overrides if present (config.local.yaml)
// This allows machine-specific settings without polluting tracked config
localConfigPath := filepath.Join(filepath.Dir(primaryConfigPath), "config.local.yaml")
if _, err := os.Stat(localConfigPath); err == nil {
v.SetConfigFile(localConfigPath)
if err := v.MergeInConfig(); err != nil {
return fmt.Errorf("error merging local config file: %w", err)
}
debug.Logf("Debug: merged local config from %s\n", localConfigPath)
// Restore primary as ConfigFileUsedView on GitHub (pinned to 71377f2769)
Solutions
- Fix the YAML in the specific file named in the error message (validate with a YAML linter).
- Check read permissions on that overlay file for the running user.
- Remove or rename the broken overlay file if it is no longer needed; Initialize tolerates missing later paths only if your code filters them beforehand.
- Diff the overlay against the primary config to spot schema drift that breaks parsing.
Example fix
// before
bd config add ./prod-overlay.yaml # file contains: {routing: mode:}
// after
# prod-overlay.yaml
routing:
mode: auto Defensive patterns
Strategy: validation
Validate before calling
for _, p := range overlayPaths {
data, err := os.ReadFile(p)
if err != nil { return err }
var m map[string]interface{}
if err := yaml.Unmarshal(data, &m); err != nil {
return fmt.Errorf("overlay %s invalid: %w", p, err)
}
} Try / catch
if err := config.Initialize(paths); err != nil {
if strings.Contains(err.Error(), "error merging config file") {
// extract path from message or re-run per-file to isolate
log.Fatalf("bad overlay config: %v", err)
}
return err
} Prevention
- Lint every overlay file in CI, not just the primary config.
- Keep overlays minimal and schema-checked against the primary config.
- Regenerate CI-produced overlays atomically to avoid half-written files.
- Diff overlays against the primary when upgrading versions.
When it happens
Trigger: Initialize receives multiple config paths; a path at index >= 1 points to a file with invalid YAML, wrong permissions, or an unreadable directory.
Common situations: Team overlays (e.g. per-environment config files) that diverge in schema; a CI-generated overlay written as JSON with a syntax error; a second config file made read-only by a package manager.
Related errors
- error reading config file: %w
- error merging local config file: %w
- persist sync.remote to config.yaml: %w
- failed to persist sync.remote to config.yaml: %w
- failed to persist sync.remote to config.yaml: %v
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2717a42e0d730d22.
Report an issue: GitHub.