gastownhall/beads · error · storage.ErrValidation
%w: metadata edits require an object: %v
Error message
%w: metadata edits require an object: %v
What it means
When applying patch.Set/patch.Unset edits, ApplyMetadataPatch unmarshals the (post-merge/replace) metadata into a map; if that document is non-null but not a JSON object, it wraps storage.ErrValidation with this message. Metadata key-level edits only make sense on an object, so any other shape is rejected.
Source
Thrown at internal/storage/issueops/aggregate.go:235
} 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 {
delete(values, key)
}
encoded, err := json.Marshal(values)
if err != nil {
return nil, false, fmt.Errorf("%w: encode metadata edits: %v", storage.ErrValidation, err)
}
next = encoded
}View on GitHub (pinned to 71377f2769)
Solutions
- Replace the metadata with a valid object first (patch.Replace) instead of doing key-level edits on a non-object.
- Verify current metadata shape (json.Unmarshal into map[string]json.RawMessage) before issuing a Set/Unset patch.
- Fix historical data: rewrite non-object metadata blobs to objects via a migration or full replace.
Example fix
// before
patch.Replace.Set = true
patch.Replace.Value = json.RawMessage(`[1,2]`)
patch.Set["k"] = json.RawMessage(`1`) // edits on non-object
// after
patch.Replace.Set = true
patch.Replace.Value = json.RawMessage(`{}`)
patch.Set["k"] = json.RawMessage(`1`) Defensive patterns
Strategy: validation
Validate before calling
func metadataIsObject(next json.RawMessage) bool {
if len(next) == 0 || string(next) == "null" { return true }
var m map[string]json.RawMessage
return json.Unmarshal(next, &m) == nil
} Try / catch
if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "require an object") { /* replace metadata with an object before Set/Unset */ } Prevention
- Don't mix key-level Set/Unset edits with non-object metadata; Replace first.
- Verify stored metadata shape before patching (unmarshal into a map).
- Prevent non-object metadata from being written at all via validation.
- Migrate legacy non-object blobs to objects before applying key edits.
When it happens
Trigger: Calling ApplyMetadataPatch with Set or Unset entries while the current metadata (after Replace/Merge steps) is a JSON array, string, number, or boolean rather than an object.
Common situations: Issues whose metadata was previously written as a bare array or scalar by an older version or direct SQL edit; mixing Replace with a non-object value and then adding Set keys; importing metadata from external tooling.
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 merge: %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/cdac6f60876e134c.
Report an issue: GitHub.