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

  1. Inspect the issue's current metadata; replace it with a valid JSON object first, then merge
  2. Read the wrapped %w cause from MergeMetadataJSON for the exact shape conflict
  3. 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

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


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