gastownhall/beads · error

parsing config: %w

Error message

parsing config: %w

What it means

Load() successfully read .beads/metadata.json but json.Unmarshal failed to decode it into the Config struct. This means the file is corrupt JSON or contains a field with the wrong JSON type (unknown string fields are tolerated, but type mismatches are not). Load fails closed to avoid feeding store selection a broken config.

Source

Thrown at internal/configfile/configfile.go:119

		}

		// 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 {
		return nil, fmt.Errorf("parsing config: %w", err)
	}

	return &cfg, nil
}

// LoadForDiscovery reads workspace metadata without migrating or rewriting it.
//
// Store admission uses this only to classify a workspace before a command can
// open storage. In particular, legacy config.json remains in place so a failed
// admission cannot turn a recoverable old workspace into a partially migrated
// one. Unknown JSON fields remain tolerated here because callers use the
// result only to decide whether to issue a conservative refusal; normal store
// selection keeps its existing validation.
func LoadForDiscovery(beadsDir string) (*Config, error) {
	data, err := os.ReadFile(ConfigPath(beadsDir)) // #nosec G304 -- beadsDir is caller-selected workspace state
	if os.IsNotExist(err) {
		data, err = os.ReadFile(filepath.Join(beadsDir, "config.json")) // #nosec G304 -- legacy workspace state
		if os.IsNotExist(err) {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Validate the file: jq . .beads/metadata.json and fix reported syntax errors
  2. Fix field types against the Config struct (ports and day counts must be numbers, not strings)
  3. Restore from backup or git; or remove metadata.json and re-run bd init (legacy config.json migration will re-run if present)
  4. Prefer LoadForDiscovery only for classification; for a broken file do not hand-write JSON — let bd regenerate it

Example fix

// before
{ "dolt_server_port": "3307", }
// after
{ "dolt_server_port": 3307 }
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile(filepath.Join(beadsDir, "metadata.json"))
if err == nil {
	var probe map[string]any
	if jerr := json.Unmarshal(data, &probe); jerr != nil {
		return fmt.Errorf("metadata.json corrupt, restore or re-init: %w", jerr)
	}
}

Type guard

func validConfigFile(path string) bool {
	data, err := os.ReadFile(path)
	if err != nil { return false }
	var v map[string]any
	return json.Unmarshal(data, &v) == nil
}

Try / catch

cfg, err := configfile.Load(beadsDir)
if err != nil {
	var typeErr *json.UnmarshalTypeError
	if errors.As(err, &typeErr) {
		return fmt.Errorf("field %s has wrong JSON type at offset %d", typeErr.Field, typeErr.Offset)
	}
	return err
}

Prevention

When it happens

Trigger: Calling configfile.Load(beadsDir) when metadata.json is truncated/torn (e.g. written by an older bd that used plain os.WriteFile), hand-edited with syntax errors, or has type mismatches like "dolt_server_port": "3307" (string instead of int).

Common situations: Workspaces touched by concurrent bd versions (pre-atomic-write builds); manual edits adding trailing commas; scripts rewriting metadata.json with wrong types; git merge conflicts leaving conflict markers in the file.

Related errors


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