gastownhall/beads · error
metadata merge failed: %w
Error message
metadata merge failed: %w
What it means
Applying a metadata merge to the issue's existing metadata failed inside storage.MergeMetadataJSON. Both the existing metadata and the merge payload must be JSON objects; the merge fails when the combined document cannot be produced (e.g. existing value is not an object or incompatible JSON shapes).
Source
Thrown at internal/storage/issueops/update.go:891
_, hasMerge := updates[OpMergeMetadata]
_, hasSet := updates[OpSetMetadata]
_, hasUnset := updates[OpUnsetMetadata]
if !hasMerge && !hasSet && !hasUnset {
return nil
}
if _, direct := resolved["metadata"]; direct {
return fmt.Errorf("cannot combine a metadata replacement with incremental metadata edits")
}
current := oldIssue.Metadata
if hasMerge {
normalized, err := storage.NormalizeMetadataValue(updates[OpMergeMetadata])
if err != nil {
return fmt.Errorf("invalid %s: %w", OpMergeMetadata, err)
}
merged, err := storage.MergeMetadataJSON(current, json.RawMessage(normalized))
if err != nil {
return fmt.Errorf("metadata merge failed: %w", err)
}
current = merged
}
if hasSet || hasUnset {
unset, err := mergeOpStrings(OpUnsetMetadata, updates[OpUnsetMetadata], hasUnset)
if err != nil {
return err
}
var merged json.RawMessage
if set, typed := updates[OpSetMetadata].(map[string]json.RawMessage); typed {
merged, err = applyTypedMetadataEdits(current, set, unset)
} else {
var set []string
set, err = mergeOpStrings(OpSetMetadata, updates[OpSetMetadata], hasSet)
if err != nil {
return err
}
merged, err = storage.ApplyMetadataEdits(current, set, unset)View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the issue's current metadata; replace it with a valid JSON object first, then merge
- Read the wrapped %w cause from MergeMetadataJSON for the exact shape conflict
- Re-fetch the issue in the same transaction to rule out concurrent modification
Example fix
// before
// issue.Metadata = '[1,2,3]' (not an object); merge fails
// after
// first update: set metadata to {}
updates := map[string]any{issueops.OpSetMetadata: map[string]any{}}
// second update: apply the merge
updates = map[string]any{issueops.OpMergeMetadata: map[string]any{"k": "v"}} Defensive patterns
Strategy: validation
Validate before calling
var cur map[string]any
if issue.Metadata != nil {
if err := json.Unmarshal(issue.Metadata, &cur); err != nil || cur == nil {
return errors.New("existing metadata is not a JSON object; do a full replacement first")
}
} Type guard
func metadataIsObject(raw json.RawMessage) bool {
var m map[string]json.RawMessage
return len(raw) > 0 && json.Unmarshal(raw, &m) == nil
} Try / catch
if err := issueops.ResolveMergeOps(issue, updates, resolved); err != nil {
if strings.Contains(err.Error(), "metadata merge failed") {
// fall back to full metadata replacement with the merged document
}
} Prevention
- Verify existing metadata is a JSON object before merging
- Migrate legacy non-object metadata values
- Re-read the issue inside the transaction to avoid racing concurrent writers
When it happens
Trigger: Calling an update with OpMergeMetadata when the issue's stored Metadata is null/non-object or the normalized merge payload cannot be merged into the current map by MergeMetadataJSON.
Common situations: Merging into legacy issues whose metadata was written as a JSON array/string by older tooling, or a race where another writer replaced metadata with a non-object between read and merge.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- metadata must be a JSON object: %w
- metadata.repo: %w
- metadata.repo must not be null
- metadata.repo must be a string, got %T
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c765675e438e63e6.
Report an issue: GitHub.