gastownhall/beads · error

metadata is not valid JSON

Error message

metadata is not valid JSON

What it means

The metadata value was an accepted Go type (string, []byte, or json.RawMessage) but its content failed json.Valid — it is not syntactically valid JSON. Storage requires metadata to be a JSON document (typically an object) so it can be queried with JSON path expressions.

Source

Thrown at internal/storage/metadata.go:32

//
// 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"
	MetadataFieldEnum   MetadataFieldType = "enum"
)

// MetadataFieldSchema defines validation rules for a single metadata field.
type MetadataFieldSchema struct {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Validate the payload locally with json.Valid before calling the API
  2. Marshal Go values with json.Marshal instead of hand-building JSON strings
  3. Fix quoting: keys and string values must use double quotes (`{"k": "v"}`)
  4. For an empty metadata set, pass "{}" rather than ""
  5. Parse the string with json.Unmarshal in a test to pin down the syntax error

Example fix

// before
updates := map[string]interface{}{"metadata": "key=value"}
// after
updates := map[string]interface{}{"metadata": `{"key":"value"}`}
Defensive patterns

Strategy: validation

Validate before calling

if !json.Valid([]byte(metaStr)) {
    return fmt.Errorf("refusing to update: metadata is not valid JSON")
}

Type guard

func validJSONObject(s string) bool {
    if !json.Valid([]byte(s)) { return false }
    var m map[string]interface{}
    return json.Unmarshal([]byte(s), &m) == nil
}

Try / catch

if err != nil && strings.Contains(err.Error(), "metadata is not valid JSON") {
    var syntaxErr *json.SyntaxError
    if errors.As(err, &syntaxErr) { /* locate offset */ }
    // Re-marshal from the source value instead of passing the raw string.
    b, merr := json.Marshal(sourceValue)
    if merr != nil { return merr }
    updates["metadata"] = json.RawMessage(b)
}

Prevention

When it happens

Trigger: Calling UpdateIssue with "metadata" set to a plain text string like "my-metadata" or "key=value", truncated JSON, single-quoted pseudo-JSON, or an empty string; also feeding partially-written buffers as []byte.

Common situations: Hand-writing metadata strings with unquoted keys or trailing commas; passing shell-arg text through CLI tooling; string concatenation that produced invalid JSON; encoding bugs producing empty or cut-off payloads.

Related errors


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