gastownhall/beads · error
error merging local config file: %w
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.
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)
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 reading config file: %w
- error merging config file %s: %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/0fb917a6ce5b343e.
Report an issue: GitHub.