gastownhall/beads · error

metadata value for key %q is not valid JSON

Error message

metadata value for key %q is not valid JSON

What it means

A value supplied for a metadata set operation is not valid JSON. Each value in a typed set edit must be a valid JSON document (json.Valid check) before it is written into the metadata object.

Source

Thrown at internal/storage/issueops/update.go:945

	if len(existing) > 0 {
		trimmed := strings.TrimSpace(string(existing))
		if trimmed != "" && trimmed != "null" {
			if err := json.Unmarshal(existing, &data); err != nil {
				return nil, fmt.Errorf("existing metadata is not a JSON object: %w", err)
			}
		}
	}
	keys := make([]string, 0, len(set))
	for key := range set {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		if err := storage.ValidateMetadataKey(key); err != nil {
			return nil, err
		}
		if !json.Valid(set[key]) {
			return nil, fmt.Errorf("metadata value for key %q is not valid JSON", key)
		}
		data[key] = set[key]
	}
	for _, key := range unset {
		if err := storage.ValidateMetadataKey(key); err != nil {
			return nil, err
		}
		delete(data, key)
	}
	result, err := json.Marshal(data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal metadata: %w", err)
	}
	return json.RawMessage(result), nil
}

// resolveNotesAppendOp folds OpAppendNotes into a concrete "notes" value on
// resolved, appending to oldIssue.Notes (read in the same mutation transaction).

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run json.Valid (or json.Marshal the value) before placing it in the set map
  2. Build values with json.Marshal(value) instead of hand-crafted strings
  3. For string values, ensure they are quoted JSON strings: json.RawMessage(`"text"`), not json.RawMessage(`text`)

Example fix

// before
set["note"] = json.RawMessage(`hello world`) // invalid JSON

// after
b, _ := json.Marshal("hello world")
set["note"] = b // "hello world"
Defensive patterns

Strategy: validation

Validate before calling

for key, val := range set {
    if !json.Valid(val) {
        return fmt.Errorf("key %q: value is not valid JSON", key)
    }
}

Type guard

func validJSONValue(raw json.RawMessage) bool { return json.Valid(raw) }

Try / catch

if err := issueops.ResolveMergeOps(issue, updates, resolved); err != nil {
    if strings.Contains(err.Error(), "is not valid JSON") {
        return fmt.Errorf("fix the metadata value before retrying: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling an update with OpSetMetadata whose map contains a json.RawMessage (or equivalent) that is malformed JSON, e.g. RawMessage built from a non-serialized Go string.

Common situations: Constructing json.RawMessage from raw user text without json.Marshal, hand-writing JSON in CLI args with quoting errors, or templating that interpolates Go values as strings instead of JSON.

Related errors


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