gastownhall/beads · error
error merging local config file
Error message
error merging local config file: %w
What it means
Initialize merges an optional machine-local override file, config.local.yaml, sitting next to the primary config, and wraps any MergeInConfig failure from it. It means the local override exists but could not be read or parsed. The viper cause is preserved via %w.
Solutions
- Open config.local.yaml (path shown via debug log, next to your primary config) and fix the YAML syntax.
- Check file permissions on config.local.yaml.
- If the overrides are not needed, delete or rename config.local.yaml so it is skipped by the os.Stat check.
- Validate with a YAML parser before saving, e.g. `yamllint config.local.yaml`.
Example fix
// before (config.local.yaml) ai: api_key: "sk-... # missing closing quote // after ai: api_key: "sk-..."
Defensive patterns
Strategy: validation
Validate before calling
local := filepath.Join(filepath.Dir(primaryConfigPath), "config.local.yaml")
if _, err := os.Stat(local); err == nil {
var m map[string]interface{}
data, _ := os.ReadFile(local)
if err := yaml.Unmarshal(data, &m); err != nil {
return fmt.Errorf("fix %s before starting: %w", local, err)
}
} Try / catch
if err := config.Initialize(paths); err != nil {
if strings.Contains(err.Error(), "config.local") {
log.Warn("local override broken, ignoring", "err", err)
os.Rename(local, local+".broken")
err = config.Initialize(paths)
}
return err
} Prevention
- Treat config.local.yaml like source code: lint it after every hand edit.
- Keep local overrides tiny — only keys that must differ per machine.
- Use an editor with YAML syntax highlighting; avoid copy-paste of secrets with quotes.
- Add config.local.yaml checks to your dev-env setup script.
When it happens
Trigger: A config.local.yaml exists in the same directory as the primary config, and its YAML is invalid or the file is unreadable when Initialize runs.
Common situations: A developer hand-edits config.local.yaml for machine-specific settings (API keys, paths) and introduces a YAML typo; the file is copied between machines with different encodings; a lockfile-style collision leaves it half-written.
Related errors
- error merging config file
- error reading config file
- failed to configure hydration
- failed to encode config.yaml
- failed to get repos config
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/0fb917a6ce5b343e.
Report an issue: GitHub.
Appendix: source
Thrown at internal/config/config.go:383
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 ConfigFileUsed
v.SetConfigFile(primaryConfigPath)
}
} else {
// No config.yaml found - use defaults and environment variables
debug.Logf("Debug: no config.yaml found; using defaults and environment variables\n")
}
return nil
}
// ResetForTesting clears the config state, allowing Initialize() to be called again.
// This is intended for tests that need to change config.yaml between test steps.
// WARNING: Not thread-safe. Only call from single-threaded test contexts.
func ResetForTesting() {
v = nilView on GitHub (pinned to 71377f2769)