gastownhall/beads · error · storage.ErrValidation
%w: metadata merge: %v
Error message
%w: metadata merge: %v
What it means
ApplyMetadataPatch wraps storage.ErrValidation with the underlying error from storage.MergeMetadataJSON when the merge overlay cannot be applied to the current metadata — typically because the overlay (or current document) is not a JSON object or is malformed. The merge is aborted and the update rejected.
Source
Thrown at internal/storage/issueops/aggregate.go:227
if patch.Replace.Set {
next = append(json.RawMessage(nil), patch.Replace.Value...)
if len(next) == 0 {
next = json.RawMessage(`{}`)
}
if !json.Valid(next) {
return nil, false, fmt.Errorf("%w: metadata replacement is not valid JSON", storage.ErrValidation)
}
} else {
next = append(json.RawMessage(nil), current...)
if patch.Merge.Set {
// A JSON null unmarshals into a nil overlay map, so the merge
// below would silently accept it as "change nothing".
if strings.TrimSpace(string(patch.Merge.Value)) == "null" {
return nil, false, fmt.Errorf("%w: metadata merge must be a JSON object", storage.ErrValidation)
}
merged, err := storage.MergeMetadataJSON(next, patch.Merge.Value)
if err != nil {
return nil, false, fmt.Errorf("%w: metadata merge: %v", storage.ErrValidation, err)
}
next = merged
}
if len(patch.Set) > 0 || len(patch.Unset) > 0 {
values := make(map[string]json.RawMessage)
if len(next) > 0 && string(next) != "null" {
if err := json.Unmarshal(next, &values); err != nil {
return nil, false, fmt.Errorf("%w: metadata edits require an object: %v", storage.ErrValidation, err)
}
}
for _, key := range setKeys {
value := patch.Set[key]
if !json.Valid(value) {
return nil, false, fmt.Errorf("%w: metadata value for key %q is not valid JSON", storage.ErrValidation, key)
}
values[key] = append(json.RawMessage(nil), value...)
}
for _, key := range patch.Unset {View on GitHub (pinned to 71377f2769)
Solutions
- Ensure the merge value is a valid JSON object (top-level { ... }) before calling.
- Validate with json.Valid and check the first non-space byte is '{' in the caller.
- If current metadata may be a non-object, replace it (patch.Replace) instead of merging.
Example fix
// before
patch.Merge.Value = json.RawMessage(`["a"]`) // array, not object
// after
overlay := map[string]json.RawMessage{"a": json.RawMessage(`1`)}
b, _ := json.Marshal(overlay)
patch.Merge.Set = true
patch.Merge.Value = b Defensive patterns
Strategy: validation
Validate before calling
func validMergeJSON(v json.RawMessage) bool {
t := strings.TrimSpace(string(v))
return json.Valid(v) && strings.HasPrefix(t, "{")
} Try / catch
if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "metadata merge") { /* fix overlay shape, no retry */ } Prevention
- Ensure merge overlays are top-level JSON objects ({...}), never arrays/scalars.
- Pre-validate with json.Valid plus a '{' prefix check.
- If existing metadata may be a non-object, use Replace instead of Merge.
- Test merge logic against the actual stored metadata shape.
When it happens
Trigger: Calling ApplyMetadataPatch with patch.Merge.Set=true and a Merge.Value that MergeMetadataJSON cannot merge (non-object JSON such as an array/string/number, or structurally invalid JSON).
Common situations: Passing a JSON array where an object is required; merging into metadata that was previously written as a non-object; user input like `--merge '[1,2]'`; version drift where old issues hold non-object metadata.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- %w: metadata replacement is not valid JSON
- %w: metadata merge must be a JSON object
- %w: metadata edits require an object: %v
- %w: metadata value for key %q is not valid JSON
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2ce10fcfd2e174e6.
Report an issue: GitHub.