gastownhall/beads · error

failed to marshal metadata: %w

Error message

failed to marshal metadata: %w

What it means

After applying set/unset edits, ApplyMetadataEdits marshals the resulting map back to JSON and wraps any json.Marshal error. Since every stored value is produced via MetadataEditValue (which marshals a string) or came from successfully-decoded existing JSON, this error is very rare and acts as a defensive guard against writing corrupt metadata.

Source

Thrown at internal/storage/metadata.go:310

		if !ok || k == "" {
			return nil, fmt.Errorf("invalid --set-metadata: expected key=value, got %q", kv)
		}
		if err := ValidateMetadataKey(k); err != nil {
			return nil, err
		}
		data[k] = MetadataEditValue(v)
	}

	for _, k := range unsetFlags {
		if err := ValidateMetadataKey(k); err != nil {
			return nil, err
		}
		delete(data, k)
	}

	result, err := json.Marshal(data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal metadata: %w", err)
	}
	return json.RawMessage(result), nil
}

// MetadataEditValue converts a --set-metadata string value to JSON. Per the CLI
// contract (GH#4146), --set-metadata values are ALWAYS stored as JSON strings;
// inferring numbers/booleans/null from string content silently broke
// map[string]string round-trips for Go consumers (a numeric-looking id or a
// version like "1e3" came back as a JSON number). Typed values go through the
// explicit --metadata / --metadata-json path (MergeMetadataJSON).
func MetadataEditValue(s string) json.RawMessage {
	b, _ := json.Marshal(s)
	return json.RawMessage(b)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure any raw values you inject are valid JSON (use json.Marshal to build them).
  2. Reload the existing metadata from the database instead of reusing a hand-modified map.
  3. If CLI-only, this indicates corrupt stored metadata — reset metadata to null and re-apply edits.
  4. Report upstream with the offending metadata blob if reproducible from library output alone.

Example fix

// before
data["x"] = json.RawMessage(`{"oops"`) // invalid
out, err := ApplyMetadataEdits(existing, nil, nil)
// after
raw, _ := json.Marshal("value")
data["x"] = raw
out, err := ApplyMetadataEdits(existing, nil, nil)
Defensive patterns

Strategy: try-catch

Validate before calling

func allValid(data map[string]json.RawMessage) bool {
    for _, v := range data {
        if !json.Valid(v) { return false }
    }
    return true
} // check the working map before/during custom edit pipelines

Type guard

func validRaw(v json.RawMessage) bool { return json.Valid(v) }

Try / catch

out, err := ApplyMetadataEdits(existing, set, unset)
if err != nil && strings.Contains(err.Error(), "failed to marshal metadata") {
    // corrupt base: rebuild metadata from the set flags alone
    out, err = ApplyMetadataEdits(nil, set, unset)
}

Prevention

When it happens

Trigger: A json.RawMessage in the working map is not valid JSON — possible if the existing metadata map was decoded from fragments that are individually invalid, or if the map was constructed by hand and passed through other code paths; essentially never fires on CLI-supplied flags.

Common situations: Programmatic callers composing json.RawMessage values manually with malformed content; corrupted stored metadata that happened to pass the initial Unmarshal through a loose path; encoder edge cases with pathological data.

Related errors


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