{"record":{"id":"c765675e438e63e6","repo":"gastownhall/beads","slug":"metadata-merge-failed-w","errorCode":null,"errorMessage":"metadata merge failed: %w","messagePattern":"metadata merge failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/update.go","lineNumber":891,"sourceCode":"\t_, hasMerge := updates[OpMergeMetadata]\n\t_, hasSet := updates[OpSetMetadata]\n\t_, hasUnset := updates[OpUnsetMetadata]\n\tif !hasMerge && !hasSet && !hasUnset {\n\t\treturn nil\n\t}\n\tif _, direct := resolved[\"metadata\"]; direct {\n\t\treturn fmt.Errorf(\"cannot combine a metadata replacement with incremental metadata edits\")\n\t}\n\n\tcurrent := oldIssue.Metadata\n\tif hasMerge {\n\t\tnormalized, err := storage.NormalizeMetadataValue(updates[OpMergeMetadata])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid %s: %w\", OpMergeMetadata, err)\n\t\t}\n\t\tmerged, err := storage.MergeMetadataJSON(current, json.RawMessage(normalized))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"metadata merge failed: %w\", err)\n\t\t}\n\t\tcurrent = merged\n\t}\n\tif hasSet || hasUnset {\n\t\tunset, err := mergeOpStrings(OpUnsetMetadata, updates[OpUnsetMetadata], hasUnset)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar merged json.RawMessage\n\t\tif set, typed := updates[OpSetMetadata].(map[string]json.RawMessage); typed {\n\t\t\tmerged, err = applyTypedMetadataEdits(current, set, unset)\n\t\t} else {\n\t\t\tvar set []string\n\t\t\tset, err = mergeOpStrings(OpSetMetadata, updates[OpSetMetadata], hasSet)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmerged, err = storage.ApplyMetadataEdits(current, set, unset)","sourceCodeStart":873,"sourceCodeEnd":909,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/update.go#L873-L909","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n// issue.Metadata = '[1,2,3]' (not an object); merge fails\n\n// after\n// first update: set metadata to {}\nupdates := map[string]any{issueops.OpSetMetadata: map[string]any{}}\n// second update: apply the merge\nupdates = map[string]any{issueops.OpMergeMetadata: map[string]any{\"k\": \"v\"}}","handlingStrategy":"validation","validationCode":"var cur map[string]any\nif issue.Metadata != nil {\n    if err := json.Unmarshal(issue.Metadata, &cur); err != nil || cur == nil {\n        return errors.New(\"existing metadata is not a JSON object; do a full replacement first\")\n    }\n}","typeGuard":"func metadataIsObject(raw json.RawMessage) bool {\n    var m map[string]json.RawMessage\n    return len(raw) > 0 && json.Unmarshal(raw, &m) == nil\n}","tryCatchPattern":"if err := issueops.ResolveMergeOps(issue, updates, resolved); err != nil {\n    if strings.Contains(err.Error(), \"metadata merge failed\") {\n        // fall back to full metadata replacement with the merged document\n    }\n}","preventionTips":["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"],"tags":["metadata","json","merge"],"backgroundTag":"metadata-merge-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}