{"record":{"id":"52ae76532d2a9f6a","repo":"gastownhall/beads","slug":"retarget-to-s-collides-with-existing-dependency-t","errorCode":null,"errorMessage":"retarget to %s collides with existing dependency target in %s","messagePattern":"retarget to (.+?) collides with existing dependency target in (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":866,"sourceCode":"\t\tSELECT 1 FROM %s moving\n\t\tJOIN %s existing ON moving.issue_id = existing.issue_id\n\t\tWHERE moving.%s = ?\n\t\t  AND (existing.%s = ? OR existing.%s = ?)\n\t\tLIMIT 1\n\t`, table, table, sourceCol, conflictCols[0], conflictCols[1])\n\n\tvar found int\n\terr := tx.QueryRowContext(ctx, query, id, id, id).Scan(&found)\n\tif err == sql.ErrNoRows {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\tif isTableNotExistError(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"check retarget collision in %s: %w\", table, err)\n\t}\n\treturn fmt.Errorf(\"retarget to %s collides with existing dependency target in %s\", id, table)\n}\n\n//nolint:gosec // G201: table and typedCol are hardcoded constants.\nfunc checkRenameTargetCollision(ctx context.Context, tx DBTX, table, typedCol, newID string) error {\n\tvar otherCols []string\n\tswitch typedCol {\n\tcase \"depends_on_issue_id\":\n\t\totherCols = []string{\"depends_on_wisp_id\", \"depends_on_external\"}\n\tcase \"depends_on_wisp_id\":\n\t\totherCols = []string{\"depends_on_issue_id\", \"depends_on_external\"}\n\tdefault:\n\t\treturn fmt.Errorf(\"checkRenameTargetCollision: unsupported typed column %q\", typedCol)\n\t}\n\n\tquery := fmt.Sprintf(`\n\t\tSELECT 1 FROM %s a\n\t\tJOIN %s b ON a.issue_id = b.issue_id\n\t\tWHERE a.%s = ?","sourceCodeStart":848,"sourceCodeEnd":884,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L848-L884","documentation":"This is a deliberate domain error: while retargeting inbound dependencies for id, checkRetargetTargetCollision found that the same (issue_id, target) edge would exist in both its current form and the retargeted form — i.e. an issue already depends on id via the destination column (issue, wisp, or external target). Performing the UPDATE would create a duplicate/conflicting edge, so the operation is aborted with this message instead.","triggerScenarios":"RetargetInboundDependenciesToWispInTx or RetargetInboundDependenciesToIssueInTx is invoked (via MoveIssuePersistenceInTx / PromoteFromEphemeralInTx) and a row exists where the same issue_id has BOTH the source-column target = id and a destination-column target (depends_on_issue_id / depends_on_wisp_id / depends_on_external) = id.","commonSituations":"An issue has parallel edges recorded against both representations of the same work item (e.g. after a partial earlier move or manual row edits); duplicate imports created shadow edges; data drift between dependencies and wisp_dependencies from an older buggy version.","solutions":["Find the conflicting rows: SELECT issue_id FROM <table> WHERE <sourceCol>='id' AND (<destCol>='id' OR depends_on_external='id').","Delete or merge the redundant duplicate edge so only one representation remains, then retry the move.","Run bd doctor / consistency checks to detect and repair duplicated dependency edges.","If caused by a prior partial migration, repair the affected rows before promoting/moving the issue."],"exampleFix":"// before: move blocked by duplicate edge\n// bd-7 depends_on bd-42 (issue) AND bd-42 (wisp)\n// after: drop the stale duplicate edge first\nDELETE FROM dependencies WHERE issue_id='bd-7' AND depends_on_wisp_id='bd-42';\nerr := MoveIssuePersistenceInTx(ctx, tx, \"bd-42\")","handlingStrategy":"validation","validationCode":"func retargetCollides(ctx context.Context, db *sql.DB, table, id string) (bool, error) {\n    const q = `SELECT 1 FROM ` + table + ` moving JOIN ` + table +\n        ` existing ON moving.issue_id = existing.issue_id\n         WHERE moving.depends_on_issue_id = ?\n           AND (existing.depends_on_wisp_id = ? OR existing.depends_on_external = ?) LIMIT 1`\n    var one int\n    err := db.QueryRowContext(ctx, q, id, id, id).Scan(&one)\n    if errors.Is(err, sql.ErrNoRows) { return false, nil }\n    return err == nil, err\n}\n// call before moving; if true, clean the duplicate edge first","typeGuard":"func isRetargetCollision(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"collides with existing dependency target\") && strings.Contains(err.Error(), \"retarget to\")\n}","tryCatchPattern":"if err := store.MoveIssue(ctx, id); isRetargetCollision(err) {\n    // surface the conflicting issue_id from the message and ask the user to dedupe\n    return fmt.Errorf(\"resolve duplicate dependency edges before moving %s: %w\", id, err)\n}","preventionTips":["Deduplicate dependency edges after imports or syncs from external tools.","Never hand-edit dependency rows; use bd dep add/remove so invariants hold.","Run periodic consistency checks (bd doctor) to catch shadow wisp/issue edges.","After any failed/interrupted move, verify no dual-representation edges were left behind."],"tags":["dependencies","data-integrity","collision","storage"],"backgroundTag":"dependency-collision","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}