gastownhall/beads · error
invalid %s: %w
Error message
invalid %s: %w
What it means
The metadata value supplied for an incremental merge operation failed normalization (storage.NormalizeMetadataValue). The error wraps the normalization failure under "invalid merge_metadata" (or the relevant op name), indicating the payload is not usable as metadata input.
Source
Thrown at internal/storage/issueops/update.go:887
// into a concrete "metadata" value on resolved, using oldIssue.Metadata (read in
// the same mutation transaction) as the base. It is a no-op when no metadata
// operation keys are present.
func resolveMetadataMergeOps(oldIssue *types.Issue, updates, resolved map[string]interface{}) error {
_, hasMerge := updates[OpMergeMetadata]
_, hasSet := updates[OpSetMetadata]
_, hasUnset := updates[OpUnsetMetadata]
if !hasMerge && !hasSet && !hasUnset {
return nil
}
if _, direct := resolved["metadata"]; direct {
return fmt.Errorf("cannot combine a metadata replacement with incremental metadata edits")
}
current := oldIssue.Metadata
if hasMerge {
normalized, err := storage.NormalizeMetadataValue(updates[OpMergeMetadata])
if err != nil {
return fmt.Errorf("invalid %s: %w", OpMergeMetadata, err)
}
merged, err := storage.MergeMetadataJSON(current, json.RawMessage(normalized))
if err != nil {
return fmt.Errorf("metadata merge failed: %w", err)
}
current = merged
}
if hasSet || hasUnset {
unset, err := mergeOpStrings(OpUnsetMetadata, updates[OpUnsetMetadata], hasUnset)
if err != nil {
return err
}
var merged json.RawMessage
if set, typed := updates[OpSetMetadata].(map[string]json.RawMessage); typed {
merged, err = applyTypedMetadataEdits(current, set, unset)
} else {
var set []string
set, err = mergeOpStrings(OpSetMetadata, updates[OpSetMetadata], hasSet)View on GitHub (pinned to 71377f2769)
Solutions
- Validate the merge payload is a JSON object before submitting (json.Valid + unmarshal into map[string]any)
- Read the wrapped %w cause for the exact normalization problem
- Convert array/scalar payloads into key/value objects
Example fix
// before
updates[issueops.OpMergeMetadata] = []string{"a", "b"} // not an object
// after
updates[issueops.OpMergeMetadata] = map[string]any{"tags": []string{"a", "b"}} Defensive patterns
Strategy: validation
Validate before calling
func validMergePayload(v any) error {
b, err := json.Marshal(v)
if err != nil { return err }
var obj map[string]any
if err := json.Unmarshal(b, &obj); err != nil {
return fmt.Errorf("merge payload must be a JSON object: %w", err)
}
return nil
} Type guard
func isJSONObject(raw json.RawMessage) bool {
var m map[string]json.RawMessage
return json.Unmarshal(raw, &m) == nil
} Try / catch
if err := storage.NormalizeMetadataValue(updates[issueops.OpMergeMetadata]); err != nil {
return fmt.Errorf("rejecting update: merge payload invalid: %w", err)
} Prevention
- Always send metadata merge payloads as JSON objects
- Marshal Go structs with json.Marshal rather than hand-writing JSON
- Validate user-supplied JSON before submitting updates
When it happens
Trigger: Passing OpMergeMetadata with a value that NormalizeMetadataValue rejects — e.g. non-JSON-serializable input, a non-object JSON document (array/string/number), or invalid types inside the object.
Common situations: Passing a JSON array or scalar where an object is expected, feeding user input that was never JSON-validated, or a CLI flag carrying malformed JSON.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- metadata.repo: %w
- metadata.repo must not be null
- loading config for gate resolution: %w
- invalid metadata: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8de3ddb15571bbff.
Report an issue: GitHub.