gastownhall/beads · error
insert replacement dependency target: %w
Error message
insert replacement dependency target: %w
What it means
This error wraps failure of the reinsert INSERT in replaceDependencyTargetInTx, which re-adds each edge with a freshly derived deterministic key depid.New(issueID, newID) so the generated depends_on_id and clone-stable primary key (#4259) stay correct. It typically means a primary/unique key conflict — an edge with the same (issue_id, newID) key already exists — or a constraint/check violation.
Source
Thrown at internal/storage/issueops/dependencies.go:761
_ = queryRows.Close()
if err := queryRows.Err(); err != nil {
return fmt.Errorf("iterate dependency targets: %w", err)
}
//nolint:gosec // table and column are hardcoded by callers.
if _, err := tx.ExecContext(ctx, fmt.Sprintf(`DELETE FROM %s WHERE %s = ? OR (%s = ? AND depends_on_external IS NULL)`, table, column, DepTargetExpr), oldID, oldID); err != nil {
return fmt.Errorf("delete old dependency target: %w", err)
}
for _, row := range rows {
// The retargeted edge's natural key is (issue_id, newID): the switch above
// set exactly one typed target column to newID. Re-derive id from it so the
// rewritten row stays merge-safe and keeps a clone-stable primary key (#4259).
//nolint:gosec // table is hardcoded by callers.
if _, err := tx.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s (id, issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external, type, created_at, created_by, metadata, thread_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, table), depid.New(row.issueID, newID), row.issueID, nullStringValue(row.issueTarget), nullStringValue(row.wispTarget), nullStringValue(row.external), row.depType, nullTimeValue(row.createdAt), nullStringValue(row.createdBy), nullStringValue(row.metadata), nullStringValue(row.threadID)); err != nil {
return fmt.Errorf("insert replacement dependency target: %w", err)
}
}
return nil
}
func nullStringValue(value sql.NullString) any {
if !value.Valid {
return nil
}
return value.String
}
func nullTimeValue(value sql.NullTime) any {
if !value.Valid {
return nil
}
return value.Time
}View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped driver error; `Duplicate entry` confirms an existing edge with the derived id
- Deduplicate: delete the pre-existing colliding edge, then retry the rename
- Check constraint violations by inspecting the row's three target columns — exactly one must be set
- Retry within the transaction; failed renames roll back, leaving original edges intact
Example fix
// before: both issues depend on bd-200; retarget inserts a duplicate id
INSERT INTO dependencies (id, issue_id, depends_on_issue_id, ...) VALUES ('bd-100->bd-200', 'bd-100', 'bd-200', ...);
-- Error 1062: Duplicate entry 'bd-100->bd-200'
// after: drop the pre-existing edge first
DELETE FROM dependencies WHERE issue_id='bd-100' AND depends_on_issue_id='bd-200';
-- then retry the rename so the reinsert succeeds Defensive patterns
Strategy: validation
Validate before calling
// Ensure no edge already exists under the post-rename deterministic id
var dupes int
db.Get(&dupes, `SELECT COUNT(*) FROM dependencies d
WHERE d.issue_id IN (SELECT depends_on_issue_id FROM dependencies WHERE depends_on_issue_id = ?)
AND d.id = CONCAT(d.issue_id, '->', ?)`, oldID, newID)
_ = dupes // or, simply: dedupe edges to the shared target before renaming
// Check one-target invariant on rows being moved
var bad int
db.Get(&bad, `SELECT COUNT(*) FROM dependencies WHERE depends_on_issue_id = ?
AND (depends_on_wisp_id IS NOT NULL OR depends_on_external IS NOT NULL)`, oldID)
if bad > 0 { return fmt.Errorf("%d rows violate one-target check; repair before rename", bad) } Try / catch
err := updateIssueOrWispID(tx, oldID, newID)
if err != nil {
var dup *mysql.MySQLError
if errors.As(err, &dup) && dup.Number == 1062 {
return fmt.Errorf("edge already exists after retarget (%s); dedupe and retry: %w", dup.Message, err)
}
return err
} Prevention
- Before renaming issue A to B, remove edges that would duplicate B's existing edges
- Rely on beads to compute dependency ids — never hand-maintain them
- After merges/clones, dedupe edges before any rename (#4259 class of bugs)
- Keep the ck_dep_one_target invariant: exactly one of the three target columns set
When it happens
Trigger: UpdateIssueIDInTx / UpdateWispIDInTx rename where the retargeted edge's id depid.New(row.issueID, newID) already exists (duplicate edge to the renamed target), or a row violates ck_dep_one_target / NOT NULL constraints after the target-column switch.
Common situations: Renaming issue A to B when both A and B already depend on the same target; merge/clone divergence leaving both old- and new-key variants of an edge; corrupt rows with all three target columns NULL hitting the one-target check.
Related errors
- rekey dependency source id %s -> %s: %w
- rekey dependency sources %s -> %s: %w
- ErrTransaction
- open unit of work: %w
- failed to begin transaction: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fcf925770b9be08f.
Report an issue: GitHub.