gastownhall/beads · error

--deps cannot attach both %q and %q to the same target %q: a

Error message

--deps cannot attach both %q and %q to the same target %q: a target can only carry one dependency type at a time. Pick one type, or open a separate issue for the second relationship (GH#4626)

What it means

`bd create --deps` normalizes each spec's (type,target) pair and silently dedupes identical duplicates, but if the SAME target appears with TWO DIFFERENT dependency types (e.g. both "blocks:bd-5" and "related:bd-5") the parse fails. The CLI deliberately rejects this because a target can carry only one dependency edge type from a single create, and the intent is ambiguous. The message cites GH#4626 for the design rationale.

Source

Thrown at cmd/bd/create_deps.go:84

// would collide on the (issue_id, target) dependency-uniqueness key with a
// *different* type. Type is not part of that key, so two different types on
// the same target can't both be stored — but the storage layer already
// treats a repeated identical (target, type) add as idempotent, so an exact
// repeat here must be deduped rather than rejected.
// GH#4626: discovered-from:X,blocked-by:X used to silently keep only one edge.
func dedupeDepSpecs(specs []domain.DependencySpec) ([]domain.DependencySpec, error) {
	// Key: swapDirection|target — same effective endpoint pair for a new issue.
	seen := make(map[string]types.DependencyType, len(specs))
	out := make([]domain.DependencySpec, 0, len(specs))
	for _, s := range specs {
		key := fmt.Sprintf("%t|%s", s.SwapDirection, s.TargetID)
		prev, ok := seen[key]
		switch {
		case !ok:
			seen[key] = s.Type
			out = append(out, s)
		case prev != s.Type:
			return nil, fmt.Errorf(
				"--deps cannot attach both %q and %q to the same target %q: a target can only carry one dependency type at a time. Pick one type, or open a separate issue for the second relationship (GH#4626)",
				prev, s.Type, s.TargetID,
			)
		default:
			// Identical edge repeated (e.g. blocked-by and depends-on both
			// normalize to the same type/target) — silently dedupe.
		}
	}
	if len(out) == 0 {
		return nil, nil
	}
	return out, nil
}

// resolveDepSpecTargets rewrites each non-external TargetID through the same
// partial-ID resolution path as `bd dep add` (utils.ResolvePartialID).
//
// Without this, `bd create --deps discovered-from:8vezf` stores the bare

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove one of the conflicting specs so the target appears once with a single type
  2. Open a separate issue or run a second `bd dep add` for the second relationship, as the message suggests
  3. Decide the intended semantics: blocked-by and depends-on are aliases of the same canonical type and dedupe harmlessly — use the alias form if both spellings meant the same edge

Example fix

// before
bd create "Task" --deps "blocks:bd-5,related:bd-5"
// after
bd create "Task" --deps "blocks:bd-5"
bd dep add bd-5 --type related --target bd-5   # second relationship separately
Defensive patterns

Strategy: validation

Validate before calling

func checkSingleTypePerTarget(deps string) error {
    seen := map[string]string{}
    for _, part := range strings.Split(deps, ",") {
        fields := strings.SplitN(strings.TrimSpace(part), ":", 2)
        var typ, target string
        if len(fields) == 2 { typ, target = fields[0], fields[1] } else { target = fields[0] }
        if prev, ok := seen[target]; ok && prev != typ {
            return fmt.Errorf("target %q has conflicting types %q and %q", target, prev, typ)
        }
        seen[target] = typ
    }
    return nil
}
// run before composing the --deps flag

Prevention

When it happens

Trigger: Calling `bd create ... --deps "blocks:bd-5,related:bd-5"` (or building the same via createIssueWithDeps/parseDepSpecs) where two specs in the comma list share TargetID but differ in Type after canonicalization (depends-on/blocked-by both canonicalize to blocks, so those duplicates are fine — only genuinely different types collide).

Common situations: Script-generated --deps lists assembled from multiple sources (e.g. CI templates plus hand-written flags) that both reference the same issue with different relation semantics; users confusing 'blocked-by X' with 'related to X' and listing both.

Related errors


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