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

  1. Set a non-empty target on the plan node's dep (a valid node key or issue ID)
  2. Validate the plan before applying: reject nodes whose deps have empty targets
  3. 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

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


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