gastownhall/beads · error
rename to %s collides with existing dependency target in %s
Error message
rename to %s collides with existing dependency target in %s
What it means
Deliberate domain error from checkRenameTargetCollision: renaming a dependency target to newID would leave some issue_id holding edges that point to newID through two different target representations at once (typed column being renamed plus the other typed column or the external column). The rename is aborted to prevent duplicate/conflicting dependency edges.
Source
Thrown at internal/storage/issueops/dependencies.go:900
SELECT 1 FROM %s a
JOIN %s b ON a.issue_id = b.issue_id
WHERE a.%s = ?
AND (b.%s = ? OR b.%s = ?)
LIMIT 1
`, table, table, typedCol, otherCols[0], otherCols[1])
var found int
err := tx.QueryRowContext(ctx, query, newID, newID, newID).Scan(&found)
if err == sql.ErrNoRows {
return nil
}
if err != nil {
if isTableNotExistError(err) {
return nil
}
return fmt.Errorf("check rename collision in %s: %w", table, err)
}
return fmt.Errorf("rename to %s collides with existing dependency target in %s", newID, table)
}
// RemoveDependencyInTx removes a dependency between two issues within an
// existing transaction. Automatically routes to wisp_dependencies if the
// source issue is an active wisp. When emitEvent is set and a row is actually
// removed it records a dependency_removed event (attributed to actor) on the
// source's event table; a no-op remove of a missing edge, or a structural remove
// with emitEvent unset, records nothing. Only the explicit bd dep remove verb
// sets emitEvent; structural removals (issue delete, reparent, batch, duplicate
// cleanup) leave it unset so they wire edges away silently, mirroring the
// proxied repository's DepInsertOpts.EmitEvent gate so both backends record
// identical history.
//
// It returns whether a dependency_removed event was actually written, so callers
// that stage tables for a Dolt commit stage the events table only when an event
// row exists (avoiding the sweep-unrelated-rows hazard doltAddAndCommit guards
// against, GH#2455).
//View on GitHub (pinned to 71377f2769)
Solutions
- Locate conflicts: SELECT a.issue_id FROM <table> a JOIN <table> b ON a.issue_id=b.issue_id WHERE a.<typedCol>='newID' AND (b.<other>='newID' OR b.depends_on_external='newID').
- Remove or rewrite the shadow edge (keep exactly one target representation) before renaming.
- Choose a different target ID if the collision stems from an ID naming clash.
- Run consistency tooling (bd doctor) to clean duplicate dependency edges after fixing.
Example fix
// before: rename bd-42 -> bd-99 collides // bd-7 has depends_on_issue_id='bd-42' AND depends_on_external='bd-99' // after: clear the stale external target first UPDATE dependencies SET depends_on_external=NULL WHERE issue_id='bd-7' AND depends_on_external='bd-99'; err := store.RenameIssue(ctx, "bd-42", "bd-99")
Defensive patterns
Strategy: validation
Validate before calling
func renameCollides(ctx context.Context, db *sql.DB, table, typedCol, otherCol, newID string) (bool, error) {
q := "SELECT 1 FROM " + table + " a JOIN " + table + " b ON a.issue_id=b.issue_id WHERE a." + typedCol +
"=? AND (b." + otherCol + "=? OR b.depends_on_external=?) LIMIT 1"
var one int
err := db.QueryRowContext(ctx, q, newID, newID, newID).Scan(&one)
if errors.Is(err, sql.ErrNoRows) { return false, nil }
return err == nil, err
}
// run for dependencies/wisp_dependencies before renaming to newID Type guard
func isRenameCollision(err error) bool {
return err != nil && strings.Contains(err.Error(), "rename to") && strings.Contains(err.Error(), "collides with existing dependency target")
} Try / catch
if err := store.RenameIssue(ctx, oldID, newID); isRenameCollision(err) {
return fmt.Errorf("pick a different ID or clear the shadow edge: %w", err)
} Prevention
- Before renaming an issue, search dependency tables for edges already targeting the new ID (typed or external).
- Avoid issuing external dependency targets that mirror internal issue IDs.
- Clean legacy dual-target rows with bd doctor before large rename batches.
- Reserve/validate new IDs against a registry to avoid accidental collisions.
When it happens
Trigger: replaceDependencyTargetInTx, the retarget helpers, or UpdateIssueIDInDependencyTargetsInTx (after an issues PK rename) finds a row pair where a.issue_id = b.issue_id, a.<typedCol> = newID, and (b.<otherTypedCol> = newID OR b.depends_on_external = newID) in dependencies or wisp_dependencies.
Common situations: Renaming an issue to an ID that another edge already references via an external/typed target on the same dependent issue; importing issues with IDs that collide with existing external dependency targets; legacy data with both a wisp edge and an issue edge for the same pair.
Related errors
- retarget to %s collides with existing dependency target in %
- delete: drop sync-plane edges into deleted wisps: %w
- capture dependency edges for rename %s -> %s: %w
- check rename collision in %s: %w
- no store is open for this workspace
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/308bf94f3b880cde.
Report an issue: GitHub.