{"record":{"id":"3f22ce7b9d5c171b","repo":"gastownhall/beads","slug":"failed-to-marshal-metadata-w","errorCode":null,"errorMessage":"failed to marshal metadata: %w","messagePattern":"failed to marshal metadata: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/update.go","lineNumber":957,"sourceCode":"\tsort.Strings(keys)\n\tfor _, key := range keys {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !json.Valid(set[key]) {\n\t\t\treturn nil, fmt.Errorf(\"metadata value for key %q is not valid JSON\", key)\n\t\t}\n\t\tdata[key] = set[key]\n\t}\n\tfor _, key := range unset {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdelete(data, key)\n\t}\n\tresult, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal metadata: %w\", err)\n\t}\n\treturn json.RawMessage(result), nil\n}\n\n// resolveNotesAppendOp folds OpAppendNotes into a concrete \"notes\" value on\n// resolved, appending to oldIssue.Notes (read in the same mutation transaction).\n// It is a no-op when the append op is absent.\nfunc resolveNotesAppendOp(oldIssue *types.Issue, updates, resolved map[string]interface{}) error {\n\traw, ok := updates[OpAppendNotes]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif _, direct := resolved[\"notes\"]; direct {\n\t\treturn fmt.Errorf(\"%w: cannot combine a notes replacement with %s\", storage.ErrValidation, OpAppendNotes)\n\t}\n\ttext, ok := raw.(string)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%s must be a string, got %T\", OpAppendNotes, raw)","sourceCodeStart":939,"sourceCodeEnd":975,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/update.go#L939-L975","documentation":"After applying metadata set/unset edits, re-marshaling the resulting map into a JSON document failed. This is nearly impossible with well-formed content (map[string]json.RawMessage marshals reliably) and usually indicates corrupted in-memory state or an unexpected value type in the map.","triggerScenarios":"applyTypedMetadataEdits finishing with a data map that json.Marshal cannot serialize — practically only when invalid entries were injected or memory/state is corrupted.","commonSituations":"Custom tooling mutating the map between edit and marshal, or embedding values json cannot handle if the map type was widened beyond json.RawMessage.","solutions":["Ensure all values stored in the metadata map are valid json.RawMessage entries","Retry the operation; a transient memory/state issue is unlikely but safe to re-run","If reproducible, dump the offending map and file a bug — valid RawMessage maps should always marshal"],"exampleFix":"// before\ndata[\"k\"] = rawMessageFromUntrustedSource // may be non-JSON bytes\n\n// after\nif !json.Valid(rawMessageFromUntrustedSource) {\n    return fmt.Errorf(\"invalid metadata value\")\n}\ndata[\"k\"] = rawMessageFromUntrustedSource","handlingStrategy":"validation","validationCode":"result, err := json.Marshal(data)\nif err != nil {\n    return fmt.Errorf(\"pre-flight marshal failed: %w\", err)\n}","typeGuard":"func marshalable(m map[string]json.RawMessage) bool {\n    _, err := json.Marshal(m)\n    return err == nil\n}","tryCatchPattern":"if err := issueops.ResolveMergeOps(issue, updates, resolved); err != nil {\n    if strings.Contains(err.Error(), \"failed to marshal metadata\") {\n        return fmt.Errorf(\"metadata map corrupted; rebuild from source data: %w\", err)\n    }\n}","preventionTips":["Keep metadata map values strictly as json.RawMessage","Validate every inserted value with json.Valid before insertion","Treat this as a bug signal — valid maps essentially never fail to marshal"],"tags":["metadata","json","serialization"],"backgroundTag":"json-marshal-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}