gastownhall/beads · error
re-parsing metadata: %w
Error message
re-parsing metadata: %w
What it means
MergeMetadataRefs merges resolved metadata_refs into an issue's existing metadata JSON. It first unmarshals the existing raw JSON into a map; if the existing blob is not a valid JSON object, the unmarshal fails and is wrapped as 're-parsing metadata'.
Source
Thrown at internal/types/types.go:1468
targetID := keyToID[target]
if targetID == "" {
targetID = target
}
if targetID == "" {
return nil, fmt.Errorf("dep target %q not found", target)
}
return NewGraphEdgeDependency(issueID, targetID, depType, "", "", "", "", nil)
}
// MergeMetadataRefs merges resolved metadata_refs into an issue's existing
// metadata JSON: each refs entry maps a metadata key to a plan-local node
// key, which is replaced with its minted ID from keyToID. Shared by the
// embedded and domain graph-apply paths.
func MergeMetadataRefs(existing json.RawMessage, refs map[string]string, keyToID map[string]string) (json.RawMessage, error) {
merged := make(map[string]json.RawMessage, len(refs))
if len(existing) > 0 {
if err := json.Unmarshal(existing, &merged); err != nil {
return nil, fmt.Errorf("re-parsing metadata: %w", err)
}
}
for metaKey, refKey := range refs {
resolvedID, ok := keyToID[refKey]
if !ok {
return nil, fmt.Errorf("metadata_ref %q references unknown key %q", metaKey, refKey)
}
idJSON, err := json.Marshal(resolvedID)
if err != nil {
return nil, fmt.Errorf("marshaling metadata ref %q: %w", metaKey, err)
}
merged[metaKey] = idJSON
}
return json.Marshal(merged)
}
// ParseWaitsForGateMetadata extracts the waits-for gate type from dependency metadata.
// Note: spawner identity comes from dependencies.depends_on_id in storage/query paths;View on GitHub (pinned to 71377f2769)
Solutions
- Validate/repair the existing metadata so it is a JSON object before merging
- If the row is legacy/corrupt, reset its metadata to '{}' and re-apply mergeMetadataRefs
- Check json.Valid(existing) as a guard before calling MergeMetadataRefs
Example fix
// before
merged, err := MergeMetadataRefs(corruptRaw, refs, keyToID) // fails
// after
if !json.Valid(corruptRaw) { corruptRaw = json.RawMessage("{}") }
merged, err := MergeMetadataRefs(corruptRaw, refs, keyToID) Defensive patterns
Strategy: validation
Validate before calling
func canMergeMetadata(existing json.RawMessage) bool {
if len(existing) == 0 { return true }
var m map[string]json.RawMessage
return json.Unmarshal(existing, &m) == nil
} Type guard
func isJSONObject(raw json.RawMessage) bool {
var m map[string]json.RawMessage
return len(raw) > 0 && json.Unmarshal(raw, &m) == nil
} Try / catch
merged, err := types.MergeMetadataRefs(existing, refs, keyToID)
if err != nil {
if strings.Contains(err.Error(), "re-parsing metadata") {
// fall back: start from an empty object
merged, err = types.MergeMetadataRefs(json.RawMessage("{}"), refs, keyToID)
}
if err != nil { return err }
} Prevention
- Always store metadata as a JSON object
- Use json.Valid / object-shape checks before persisting external metadata
- Reset corrupted metadata rows to '{}' before merging
When it happens
Trigger: Calling MergeMetadataRefs with existing metadata that is not valid JSON or is not a JSON object (e.g. a JSON array, string, or garbage bytes).
Common situations: Rows written by older versions or external tools with non-object metadata; corrupted metadata columns; hand-edited metadata values in the database.
Related errors
- parsing waits-for metadata to set also_blocks: %w
- ExternalDoltConfig: must set Socket or (Host, Port)
- failed to parse teams response: %w
- failed to parse projects response: %w
- failed to parse create project response: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8f5bcb8d95bcca61.
Report an issue: GitHub.