{"record":{"id":"682b159c37a1b23c","repo":"gastownhall/beads","slug":"rekey-dependency-source-id-s-s-w","errorCode":null,"errorMessage":"rekey dependency source id %s -> %s: %w","messagePattern":"rekey dependency source id (.+?) -> (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":670,"sourceCode":"\t\t\treturn fmt.Errorf(\"scan dependency source: %w\", err)\n\t\t}\n\t\ttarget, ok := resolveDependencyTarget(issueTarget, wispTarget, external)\n\t\tif !ok {\n\t\t\tcontinue // ck_dep_one_target guarantees one target; skip defensively\n\t\t}\n\t\tif want := depid.New(newID, target); want != id {\n\t\t\trekeys = append(rekeys, rekey{oldRowID: id, newRowID: want})\n\t\t}\n\t}\n\t_ = queryRows.Close()\n\tif err := queryRows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"iterate dependency sources: %w\", err)\n\t}\n\tfor _, rk := range rekeys {\n\t\tif _, err := tx.ExecContext(ctx,\n\t\t\t\"UPDATE dependencies SET id = ?, issue_id = ? WHERE id = ?\",\n\t\t\trk.newRowID, newID, rk.oldRowID); err != nil {\n\t\t\treturn fmt.Errorf(\"rekey dependency source id %s -> %s: %w\", rk.oldRowID, rk.newRowID, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// resolveDependencyTarget returns the single non-null dependency target — the value\n// depid.New and the uk_dep_* unique keys treat as the edge's target — following the\n// same precedence as DepTargetExpr (issue, then wisp, then external).\nfunc resolveDependencyTarget(issueTarget, wispTarget, external sql.NullString) (string, bool) {\n\tswitch {\n\tcase issueTarget.Valid:\n\t\treturn issueTarget.String, true\n\tcase wispTarget.Valid:\n\t\treturn wispTarget.String, true\n\tcase external.Valid:\n\t\treturn external.String, true\n\tdefault:\n\t\treturn \"\", false","sourceCodeStart":652,"sourceCodeEnd":688,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L652-L688","documentation":"This error wraps failure of the per-row UPDATE that rewrites a stale dependency primary key: `UPDATE dependencies SET id = ?, issue_id = ? WHERE id = ?` in rekeyDependencySourceInTx. The most common cause is a primary/unique key conflict — the desired new id depid.New(newID, target) already exists on another row (a duplicate edge).","triggerScenarios":"UpdateIssueIDInTx rename where, for some edge, depid.New(newID, target) collides with the id of an existing dependency row — e.g. both bd-101 and bd-100 already have an edge to the same target, or a prior partial rekey left overlapping rows.","commonSituations":"Renaming an issue to an ID that already has edges to the same targets; clone/merge divergence (#4259 scenario) leaving both stale and fresh key variants of the same edge; concurrent renames racing on the same rows.","solutions":["Read the wrapped driver error; `Duplicate entry ... for key PRIMARY` confirms an id collision","Find the colliding row: SELECT id, issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external FROM dependencies WHERE id = depid.New(newID, target)","Remove the duplicate edge before renaming (it will be re-created by the rekey)","Retry inside one transaction — partial rekeys roll back, so no manual cleanup of half-updated ids should be needed"],"exampleFix":"// before: rename collides with existing edge id\nUPDATE dependencies SET id='bd-100->bd-200', issue_id='bd-100' WHERE id='bd-101->bd-200';\n-- Error 1062: Duplicate entry 'bd-100->bd-200' for key 'PRIMARY'\n// after: deduplicate first\nDELETE FROM dependencies WHERE id='bd-100->bd-200';  -- then retry the rename","handlingStrategy":"validation","validationCode":"// Pre-check that no existing edge already carries the post-rename deterministic id\nfor _, target := range sourceTargets(oldID) {\n    want := depid.New(newID, target)\n    var exists bool\n    db.Get(&exists, `SELECT COUNT(*) > 0 FROM dependencies WHERE id = ? AND issue_id <> ?`, want, oldID)\n    if exists {\n        return fmt.Errorf(\"edge %s already exists; remove the duplicate before renaming %s to %s\", want, oldID, newID)\n    }\n}","typeGuard":null,"tryCatchPattern":"err := updateIssueID(tx, oldID, newID)\nif err != nil {\n    var dup *mysql.MySQLError\n    if errors.As(err, &dup) && dup.Number == 1062 {\n        return fmt.Errorf(\"remove duplicate dependency edge %q before renaming: %w\", extractKey(dup), err)\n    }\n    return err\n}","preventionTips":["Deduplicate edges to shared targets before renaming issues","Never manually edit dependencies.id; always let beads derive it via depid.New","Run one rename at a time; concurrent renames can mint identical keys","After merges/clones, run bd doctor to catch stale vs fresh key variants"],"tags":["database","primary-key","duplicate","transaction"],"backgroundTag":"duplicate-primary-key-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}