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

  1. Validate/repair the existing metadata so it is a JSON object before merging
  2. If the row is legacy/corrupt, reset its metadata to '{}' and re-apply mergeMetadataRefs
  3. 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

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


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