gastownhall/beads · error
dep target %q not found
Error message
dep target %q not found
What it means
NewGraphNodeDependency resolves a graph-plan node's inline dependency target: first as a plan-local key via keyToID, then as a literal issue ID. If the target is neither a known key nor a non-empty ID (i.e. resolves to the empty string), the dependency cannot be built and this error is returned.
Source
Thrown at internal/types/types.go:1455
return dep, nil
}
// NewGraphNodeDependency builds the dependency record for a graph-plan node's
// inline dep, shared by the embedded and domain apply paths so their
// resolution semantics cannot drift: an empty type defaults to blocks, and
// the target resolves as a plan-local key first, then as a literal issue ID.
// Waits-for deps carry gate metadata like waits-for edges (all-children
// default, no explicit spawner).
func NewGraphNodeDependency(issueID string, depType DependencyType, target string, keyToID map[string]string) (*Dependency, error) {
if depType == "" {
depType = DepBlocks
}
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Set a non-empty target on the plan node's dep (a valid node key or issue ID)
- Validate the plan before applying: reject nodes whose deps have empty targets
- Ensure keyToID is populated before resolution if the target is meant to be a plan-local key
Example fix
// before
"deps": [{"target": ""}] // empty target
// after
"deps": [{"target": "bd-a3f8e9"}] // or a plan-local node key Defensive patterns
Strategy: validation
Validate before calling
func validateDepTarget(target string, keyToID map[string]string) error {
if target == "" && keyToID[target] == "" {
return fmt.Errorf("dep target must be a plan node key or issue ID, got empty string")
}
return nil
} Type guard
func depTargetResolvable(target string, keyToID map[string]string) bool {
return target != ""
} Try / catch
dep, err := types.NewGraphNodeDependency(issueID, depType, target, keyToID)
if err != nil {
if strings.Contains(err.Error(), "dep target") && strings.Contains(err.Error(), "not found") {
return fmt.Errorf("plan validation: %w", err)
}
return err
} Prevention
- Validate plans before apply: every dep must have a non-empty target
- Keep keyToID populated from the full node set before resolving deps
- Normalize target fields without dropping empty-string checks
When it happens
Trigger: Calling NewGraphNodeDependency with target="" (and target not present in keyToID), so both the keyToID lookup and the literal fallback yield "".
Common situations: A graph plan YAML/JSON node declares a dep with an empty target field, or code that strips/normalizes the target reduces it to empty before the call.
Related errors
- db: DependencySQLRepository.Insert: dep must not be nil
- db: DependencySQLRepository.Insert: IssueID must not be empt
- db: DependencySQLRepository.Insert: DependsOnID must not be
- db: DependencySQLRepository.ValidateBlockingHierarchy: dep m
- failed to link digest to root: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ce826edf7093b43e.
Report an issue: GitHub.