gastownhall/beads · error
config not initialized
Error message
config not initialized
What it means
SaveConfigValue requires the package-level viper instance to have been initialized by calling Initialize first. If the singleton `v` is still nil, saving is impossible and this sentinel error is returned. It is a programming/lifecycle error, not a data error.
Source
Thrown at internal/config/config.go:635
case SourceFlag:
overrideDesc = "command-line flag"
case SourceEnvVar:
overrideDesc = "environment variable"
default:
overrideDesc = string(override.OverriddenBy)
}
// Always emit to stderr when verbose mode is enabled (caller guards on verbose)
fmt.Fprintf(os.Stderr, "Config: %s overridden by %s (was: %v from %s, now: %v)\n",
override.Key, overrideDesc, override.OriginalValue, sourceDesc, override.EffectiveValue)
}
// SaveConfigValue sets a key-value pair and writes it to the config file.
// If no config file is currently loaded, it creates config.yaml in the given beadsDir.
// Only the specified key is modified; other file contents are preserved.
func SaveConfigValue(key string, value interface{}, beadsDir string) error {
if v == nil {
return fmt.Errorf("config not initialized")
}
v.Set(key, value)
configPath := v.ConfigFileUsed()
if configPath == "" {
configPath = filepath.Join(beadsDir, "config.yaml")
v.SetConfigFile(configPath)
}
// Read existing file contents to avoid dumping all merged viper state
// (defaults, env vars, overrides) into the config file.
existing := make(map[string]interface{})
if data, err := os.ReadFile(filepath.Clean(configPath)); err == nil {
_ = yaml.Unmarshal(data, &existing)
}
// Set the single key using dot-path splitting for nested keys (e.g. "routing.mode").
setNestedKey(existing, key, value)View on GitHub (pinned to 71377f2769)
Solutions
- Call config.Initialize (with the appropriate config paths/beads dir) before SaveConfigValue.
- Check the error returned by Initialize — if it failed, `v` may remain nil.
- In tests, use the same setup helper other config tests use to initialize the singleton before saving.
Example fix
// before
err := config.SaveConfigValue("routing.mode", "auto", beadsDir)
// after
if err := config.Initialize([]string{configPath}); err != nil {
return err
}
err := config.SaveConfigValue("routing.mode", "auto", beadsDir) Defensive patterns
Strategy: try-catch
Validate before calling
// ensure config is initialized in main/setup before any save:
if err := config.Initialize(configPaths); err != nil {
return fmt.Errorf("config bootstrap failed: %w", err)
} Try / catch
if err := config.SaveConfigValue(key, value, beadsDir); err != nil {
if err.Error() == "config not initialized" {
if ierr := config.Initialize(configPaths); ierr != nil {
return ierr
}
return config.SaveConfigValue(key, value, beadsDir)
}
return err
} Prevention
- Centralize config bootstrap in one entry point (main or a setup package).
- Never call save APIs from init() or package-level vars that run before Initialize.
- In tests, share a setup helper that initializes config before save calls.
- Check the error from Initialize and fail fast instead of continuing with nil state.
When it happens
Trigger: Calling SaveConfigValue without a prior successful call to Initialize in the same process, or after Initialize failed before assigning `v`.
Common situations: A CLI subcommand or test calls SaveConfigValue directly, bypassing the normal bootstrap; a plugin invokes the save API in a process where config was never loaded.
Related errors
- no database configuration found
- no config.yaml found in %s (run 'bd init' first)
- GitHub tracker not initialized
- Jira tracker not initialized
- Linear tracker not initialized
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5ee7037487194539.
Report an issue: GitHub.