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.

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.

Appendix: 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)