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

  1. Call config.Initialize (with the appropriate config paths/beads dir) before SaveConfigValue.
  2. Check the error returned by Initialize — if it failed, `v` may remain nil.
  3. 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

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


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