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 bareView on GitHub (pinned to 71377f2769)
Solutions
- Remove one of the conflicting specs so the target appears once with a single type
- Open a separate issue or run a second `bd dep add` for the second relationship, as the message suggests
- 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
- Build the deps list from a map keyed by target so each target can only carry one type
- Remember depends-on/blocked-by are aliases of blocks — don't mix them with other types on the same target
- Emit the second relationship via a separate `bd dep add` call
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
- --deps target is empty
- invalid dependency type %q (must be non-empty, max %d chars)
- unknown dependency type %q; valid types: %s
- bulk dependency validation failed: %s
- edge %d %s->%s duplicates a parent-child relationship with d
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/0d67600d818e36c2.
Report an issue: GitHub.