gastownhall/beads · error
failed to marshal merged metadata: %w
Error message
failed to marshal merged metadata: %w
What it means
After merging, MergeMetadataJSON marshals the combined map[string]json.RawMessage back to JSON; this error wraps any json.Marshal failure. It is rare because the map was just populated by successfully-unmarshaled JSON; it mainly fires when a stored value inside the map is itself invalid raw JSON. The library throws it as a safety net rather than writing corrupt metadata.
Source
Thrown at internal/storage/metadata.go:271
if trimmed != "" && trimmed != "null" {
if err := json.Unmarshal(existing, &base); err != nil {
return nil, fmt.Errorf("existing metadata is not a JSON object: %w", err)
}
}
}
overlay := make(map[string]json.RawMessage)
if err := json.Unmarshal(incoming, &overlay); err != nil {
return nil, fmt.Errorf("new metadata is not a JSON object: %w", err)
}
for k, v := range overlay {
base[k] = v
}
result, err := json.Marshal(base)
if err != nil {
return nil, fmt.Errorf("failed to marshal merged metadata: %w", err)
}
return json.RawMessage(result), nil
}
// ApplyMetadataEdits applies incremental set (key=value) and unset (key) edits
// to existing metadata and returns the merged JSON. Set values are typed via
// MetadataEditValue; keys are validated with ValidateMetadataKey.
func ApplyMetadataEdits(existing json.RawMessage, setFlags, unsetFlags []string) (json.RawMessage, error) {
data := make(map[string]json.RawMessage)
if len(existing) > 0 {
trimmed := strings.TrimSpace(string(existing))
if trimmed != "" && trimmed != "null" {
if err := json.Unmarshal(existing, &data); err != nil {
return nil, fmt.Errorf("existing metadata is not a JSON object: %w", err)
}
}
}
View on GitHub (pinned to 71377f2769)
Solutions
- Validate every raw value fragment is itself valid JSON (json.Valid) before composing the map.
- Re-obtain the existing metadata from storage instead of reusing a mutated copy.
- If programmatically building values, produce them with json.Marshal, never string concatenation.
- Report upstream if it reproduces on data produced solely by MergeMetadataJSON — that indicates corrupt stored metadata.
Example fix
// before
base["cfg"] = json.RawMessage(`{broken`) // invalid fragment
merged, err := MergeMetadataJSON(existing, incoming)
// after
v, _ := json.Marshal(map[string]string{"a": "b"})
base["cfg"] = v
merged, err := MergeMetadataJSON(existing, incoming) Defensive patterns
Strategy: try-catch
Validate before calling
for k, v := range fragments {
if !json.Valid(v) {
return fmt.Errorf("fragment %q is not valid JSON", k)
}
} // run on any hand-built json.RawMessage map before merging Type guard
func validRaw(v json.RawMessage) bool { return json.Valid(v) } Try / catch
result, err := MergeMetadataJSON(existing, incoming)
if err != nil && strings.Contains(err.Error(), "failed to marshal merged metadata") {
// stored data is corrupt: start fresh from incoming only
result, err = MergeMetadataJSON(nil, incoming)
} Prevention
- Never build json.RawMessage by string concatenation; always json.Marshal.
- Treat this error as a corruption signal: reload the original metadata from storage.
- Add json.Valid assertions in tests for any code composing metadata fragments.
When it happens
Trigger: The merged base map contains a json.RawMessage value that is not valid JSON (possible when existing was decoded loosely or constructed manually and then re-fed), or an out-of-memory/unsupported-value edge during Marshal.
Common situations: Re-merging output that was produced by an external tool and partially rewritten; programmatically composing json.RawMessage values by hand with malformed fragments; extremely deep nesting hitting encoder limits.
Related errors
- marshaling Linear milestone metadata: %w
- failed to marshal metadata: %w
- failed to marshal metadata: %w
- marshaling metadata ref %q: %w
- ExternalDoltConfig: must set Socket or (Host, Port)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d9a56234b0ade5c7.
Report an issue: GitHub.