gastownhall/beads · error
metadata must be string, []byte, or json.RawMessage, got %T
Error message
metadata must be string, []byte, or json.RawMessage, got %T
What it means
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.
Source
Thrown at internal/storage/metadata.go:27
)
// NormalizeMetadataValue converts metadata values to a validated JSON string.
// Accepts string, []byte, or json.RawMessage and returns a validated JSON string.
// Returns an error if the value is not valid JSON or is an unsupported type.
//
// This supports GH#1417: allow UpdateIssue metadata updates via json.RawMessage/[]byte.
func NormalizeMetadataValue(value interface{}) (string, error) {
var jsonStr string
switch v := value.(type) {
case string:
jsonStr = v
case []byte:
jsonStr = string(v)
case json.RawMessage:
jsonStr = string(v)
default:
return "", fmt.Errorf("metadata must be string, []byte, or json.RawMessage, got %T", value)
}
// Validate that it's valid JSON
if !json.Valid([]byte(jsonStr)) {
return "", fmt.Errorf("metadata is not valid JSON")
}
return jsonStr, nil
}
// MetadataFieldType defines the type of a metadata field for schema validation.
type MetadataFieldType string
const (
MetadataFieldString MetadataFieldType = "string"
MetadataFieldInt MetadataFieldType = "int"
MetadataFieldFloat MetadataFieldType = "float"
MetadataFieldBool MetadataFieldType = "bool"View on GitHub (pinned to 71377f2769)
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
Example fix
// before
updates := map[string]interface{}{"metadata": map[string]interface{}{"key": "value"}}
// after
b, _ := json.Marshal(map[string]interface{}{"key": "value"})
updates := map[string]interface{}{"metadata": json.RawMessage(b)} Defensive patterns
Strategy: type-guard
Validate before calling
func safeMetadata(v interface{}) (string, error) {
switch v.(type) {
case string, []byte, json.RawMessage, nil:
// nil still fails NormalizeMetadataValue — use "{}" to clear
default:
b, err := json.Marshal(v)
if err != nil { return "", err }
return string(b), nil
}
return "", nil
} Type guard
func isMetadataValue(v interface{}) bool {
switch v.(type) {
case string, []byte, json.RawMessage:
return true
default:
return false
}
} Try / catch
if err != nil && strings.Contains(err.Error(), "metadata must be string, []byte, or json.RawMessage") {
b, merr := json.Marshal(originalValue)
if merr != nil { return merr }
updates["metadata"] = json.RawMessage(b)
err = retryUpdate(updates)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- %w: metadata replacement is not valid JSON
- %w: metadata merge must be a JSON object
- %w: metadata merge: %v
- %w: metadata edits require an object: %v
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/da893cc6d2a8a87b.
Report an issue: GitHub.