gastownhall/beads · error
failed to marshal metadata: %w
Error message
failed to marshal metadata: %w
What it means
After applying metadata set/unset edits, re-marshaling the resulting map into a JSON document failed. This is nearly impossible with well-formed content (map[string]json.RawMessage marshals reliably) and usually indicates corrupted in-memory state or an unexpected value type in the map.
Source
Thrown at internal/storage/issueops/update.go:957
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).
// It is a no-op when the append op is absent.
func resolveNotesAppendOp(oldIssue *types.Issue, updates, resolved map[string]interface{}) error {
raw, ok := updates[OpAppendNotes]
if !ok {
return nil
}
if _, direct := resolved["notes"]; direct {
return fmt.Errorf("%w: cannot combine a notes replacement with %s", storage.ErrValidation, OpAppendNotes)
}
text, ok := raw.(string)
if !ok {
return fmt.Errorf("%s must be a string, got %T", OpAppendNotes, raw)View on GitHub (pinned to 71377f2769)
Solutions
- Ensure all values stored in the metadata map are valid json.RawMessage entries
- Retry the operation; a transient memory/state issue is unlikely but safe to re-run
- If reproducible, dump the offending map and file a bug — valid RawMessage maps should always marshal
Example fix
// before
data["k"] = rawMessageFromUntrustedSource // may be non-JSON bytes
// after
if !json.Valid(rawMessageFromUntrustedSource) {
return fmt.Errorf("invalid metadata value")
}
data["k"] = rawMessageFromUntrustedSource Defensive patterns
Strategy: validation
Validate before calling
result, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("pre-flight marshal failed: %w", err)
} Type guard
func marshalable(m map[string]json.RawMessage) bool {
_, err := json.Marshal(m)
return err == nil
} Try / catch
if err := issueops.ResolveMergeOps(issue, updates, resolved); err != nil {
if strings.Contains(err.Error(), "failed to marshal metadata") {
return fmt.Errorf("metadata map corrupted; rebuild from source data: %w", err)
}
} Prevention
- Keep metadata map values strictly as json.RawMessage
- Validate every inserted value with json.Valid before insertion
- Treat this as a bug signal — valid maps essentially never fail to marshal
When it happens
Trigger: applyTypedMetadataEdits finishing with a data map that json.Marshal cannot serialize — practically only when invalid entries were injected or memory/state is corrupted.
Common situations: Custom tooling mutating the map between edit and marshal, or embedding values json cannot handle if the map type was widened beyond json.RawMessage.
Related errors
- marshaling Linear milestone metadata: %w
- failed to marshal merged metadata: %w
- failed to marshal metadata: %w
- marshaling metadata ref %q: %w
- ExternalDoltConfig: must set Socket or (Host, Port)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3f22ce7b9d5c171b.
Report an issue: GitHub.