gastownhall/beads · error
metadata_ref %q references unknown key %q
Error message
metadata_ref %q references unknown key %q
What it means
Each entry in refs maps a metadata key to a plan-local node key that must be translated to its minted issue ID via keyToID. If the referenced key is not in keyToID, MergeMetadataRefs fails with this error naming both the metadata key and the unknown reference key.
Source
Thrown at internal/types/types.go:1474
}
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;
// metadata.spawner_id is parsed for compatibility/future explicit targeting.
// Returns WaitsForAllChildren on empty/invalid metadata for backward compatibility.
func ParseWaitsForGateMetadata(metadata string) string {
if strings.TrimSpace(metadata) == "" {
return WaitsForAllChildren
}View on GitHub (pinned to 71377f2769)
Solutions
- Add/fix the referenced node in the plan so its key exists in keyToID
- Correct the metadata_refs entry to point at an existing plan node key
- Regenerate the plan so refs and nodes stay in sync
Example fix
// before
"metadata_refs": {"spawner": "node-7"} // node-7 not in plan
// after
"metadata_refs": {"spawner": "node-3"} // node-3 exists as a plan node Defensive patterns
Strategy: validation
Validate before calling
func validateMetadataRefs(refs map[string]string, keyToID map[string]string) error {
for metaKey, refKey := range refs {
if _, ok := keyToID[refKey]; !ok {
return fmt.Errorf("metadata_refs[%q] points at unknown plan key %q", metaKey, refKey)
}
}
return nil
} Type guard
func refsResolvable(refs, keyToID map[string]string) bool {
for _, refKey := range refs {
if _, ok := keyToID[refKey]; !ok { return false }
}
return true
} Try / catch
merged, err := types.MergeMetadataRefs(existing, refs, keyToID)
if err != nil {
if strings.Contains(err.Error(), "references unknown key") {
return fmt.Errorf("plan validation failed: %w", err)
}
return err
} Prevention
- Generate metadata_refs from the same node list that builds keyToID
- Validate all refs against node keys during plan load, before minting IDs
- Regenerate plans wholesale after renaming or deleting nodes
When it happens
Trigger: Calling MergeMetadataRefs where some refs[metaKey] value is not a key present in the keyToID map.
Common situations: A graph plan's metadata_refs section points at a node key that was renamed, deleted, or never declared; applying a truncated or partially-generated plan.
Related errors
- serializing waits-for metadata: unresolved spawner key %q
- dep target %q not found
- no store is open for this workspace
- not found
- no absolute native user directory is available
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/37eeae96227fda00.
Report an issue: GitHub.