gastownhall/beads · error

parsing legacy config: %w

Error message

parsing legacy config: %w

What it means

Load() in internal/configfile reads .beads/metadata.json; if absent, it falls back to the legacy config.json and migrates it. This error wraps a json.Unmarshal failure on that legacy file, meaning the old config.json exists but is not valid JSON (or has incompatible types for Config fields). Load aborts rather than silently dropping workspace settings.

Source

Thrown at internal/configfile/configfile.go:100

func Load(beadsDir string) (*Config, error) {
	configPath := ConfigPath(beadsDir)

	data, err := os.ReadFile(configPath) // #nosec G304 - controlled path from config
	if os.IsNotExist(err) {
		// Try legacy config.json location (migration path)
		legacyPath := filepath.Join(beadsDir, "config.json")
		data, err = os.ReadFile(legacyPath) // #nosec G304 - controlled path from config
		if os.IsNotExist(err) {
			return nil, nil
		}
		if err != nil {
			return nil, fmt.Errorf("reading legacy config: %w", err)
		}

		// Migrate: parse legacy config, save as metadata.json, remove old file
		var cfg Config
		if err := json.Unmarshal(data, &cfg); err != nil {
			return nil, fmt.Errorf("parsing legacy config: %w", err)
		}

		// Save to new location
		if err := cfg.Save(beadsDir); err != nil {
			return nil, fmt.Errorf("migrating config to metadata.json: %w", err)
		}

		// Remove legacy file (best effort: migration already saved to new location)
		_ = os.Remove(legacyPath)

		return &cfg, nil
	}
	if err != nil {
		return nil, fmt.Errorf("reading config: %w", err)
	}

	var cfg Config
	if err := json.Unmarshal(data, &cfg); err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Validate/repair config.json with a JSON linter (jq . config.json) and fix syntax errors
  2. Check field value types against the Config struct in internal/configfile/configfile.go (e.g. database must be a string)
  3. Restore config.json from backup or version control
  4. Delete config.json and recreate config via bd init (migration will start fresh with defaults)

Example fix

// before
{ "database": 123, }   // trailing comma, wrong type
// after
{ "database": "beads.db" }
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile(filepath.Join(beadsDir, "config.json"))
if err == nil {
	var probe map[string]any
	if jerr := json.Unmarshal(data, &probe); jerr != nil {
		return fmt.Errorf("legacy config.json is not valid JSON, fix before running bd: %w", jerr)
	}
}

Type guard

func isJSON(data []byte) bool {
	var v any
	return json.Unmarshal(data, &v) == nil
}

Try / catch

cfg, err := configfile.Load(beadsDir)
if err != nil {
	var syntaxErr *json.SyntaxTypeError
	var typeErr *json.UnmarshalTypeError
	switch {
	case errors.As(err, &syntaxErr):
		// point user at config.json offset in syntaxErr.Offset
	case errors.As(err, &typeErr):
		// wrong type at typeErr.Field
	}
	return err
}

Prevention

When it happens

Trigger: Calling configfile.Load(beadsDir) when metadata.json is missing, config.json exists, and config.json contains malformed JSON (truncated write, hand-edited syntax error, non-JSON content) or fields whose JSON types don't match Config (e.g. "database": 123).

Common situations: Workspaces created by old bd versions migrated after a manual edit; interrupted writes to config.json from a crash or full disk; a user placing notes or YAML in config.json; older metadata fields with changed types.

Related errors


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