{"record":{"id":"da893cc6d2a8a87b","repo":"gastownhall/beads","slug":"metadata-must-be-string-byte-or-json-rawmessag","errorCode":null,"errorMessage":"metadata must be string, []byte, or json.RawMessage, got %T","messagePattern":"metadata must be string, \\[\\]byte, or json\\.RawMessage, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/metadata.go","lineNumber":27,"sourceCode":")\n\n// NormalizeMetadataValue converts metadata values to a validated JSON string.\n// Accepts string, []byte, or json.RawMessage and returns a validated JSON string.\n// Returns an error if the value is not valid JSON or is an unsupported type.\n//\n// This supports GH#1417: allow UpdateIssue metadata updates via json.RawMessage/[]byte.\nfunc NormalizeMetadataValue(value interface{}) (string, error) {\n\tvar jsonStr string\n\n\tswitch v := value.(type) {\n\tcase string:\n\t\tjsonStr = v\n\tcase []byte:\n\t\tjsonStr = string(v)\n\tcase json.RawMessage:\n\t\tjsonStr = string(v)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"metadata must be string, []byte, or json.RawMessage, got %T\", value)\n\t}\n\n\t// Validate that it's valid JSON\n\tif !json.Valid([]byte(jsonStr)) {\n\t\treturn \"\", fmt.Errorf(\"metadata is not valid JSON\")\n\t}\n\n\treturn jsonStr, nil\n}\n\n// MetadataFieldType defines the type of a metadata field for schema validation.\ntype MetadataFieldType string\n\nconst (\n\tMetadataFieldString MetadataFieldType = \"string\"\n\tMetadataFieldInt    MetadataFieldType = \"int\"\n\tMetadataFieldFloat  MetadataFieldType = \"float\"\n\tMetadataFieldBool   MetadataFieldType = \"bool\"","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/metadata.go#L9-L45","documentation":"NormalizeMetadataValue was given a metadata value whose Go type is not one of the accepted types (string, []byte, json.RawMessage). The storage layer stores metadata as a JSON string, so callers must pass one of those shapes; e.g. passing a map[string]interface{} or a struct directly is rejected with the actual %T type named in the message.","triggerScenarios":"Calling UpdateIssue with a \"metadata\" field set to map[string]interface{}, struct, nil, json.Number, or any type other than string/[]byte/json.RawMessage; also via ApplyMetadataEdits / CompareAndSet paths that funnel through NormalizeMetadataValue.","commonSituations":"Passing an already-decoded map from an API response instead of re-marshaling it; passing nil to clear metadata; upgrading code that previously accepted objects; building metadata values in tests with Go literals.","solutions":["Marshal the value first: b, _ := json.Marshal(v), then pass json.RawMessage(b) or string(b)","If passing a string, ensure it is JSON text (an object literal like `{\"k\":\"v\"}`), not raw key=value text","For clearing metadata pass the string \"{}\" or \"null\" (both are valid JSON), not nil","Check the %T in the error message to see which type was actually passed"],"exampleFix":"// before\nupdates := map[string]interface{}{\"metadata\": map[string]interface{}{\"key\": \"value\"}}\n// after\nb, _ := json.Marshal(map[string]interface{}{\"key\": \"value\"})\nupdates := map[string]interface{}{\"metadata\": json.RawMessage(b)}","handlingStrategy":"type-guard","validationCode":"func safeMetadata(v interface{}) (string, error) {\n    switch v.(type) {\n    case string, []byte, json.RawMessage, nil:\n        // nil still fails NormalizeMetadataValue — use \"{}\" to clear\n    default:\n        b, err := json.Marshal(v)\n        if err != nil { return \"\", err }\n        return string(b), nil\n    }\n    return \"\", nil\n}","typeGuard":"func isMetadataValue(v interface{}) bool {\n    switch v.(type) {\n    case string, []byte, json.RawMessage:\n        return true\n    default:\n        return false\n    }\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"metadata must be string, []byte, or json.RawMessage\") {\n    b, merr := json.Marshal(originalValue)\n    if merr != nil { return merr }\n    updates[\"metadata\"] = json.RawMessage(b)\n    err = retryUpdate(updates)\n}","preventionTips":["Always pass metadata as string, []byte, or json.RawMessage — never maps or structs","Marshal structs/maps with json.Marshal before assigning to the metadata field","Use \"{}\" or \"null\" (valid JSON) to clear metadata, not nil","Read the %T in the error message to identify the offending type at the call site"],"tags":["go","metadata","json","type-mismatch","validation"],"backgroundTag":"metadata-type-unsupported","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}