{"record":{"id":"fcf925770b9be08f","repo":"gastownhall/beads","slug":"insert-replacement-dependency-target-w","errorCode":null,"errorMessage":"insert replacement dependency target: %w","messagePattern":"insert replacement dependency target: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":761,"sourceCode":"\t_ = queryRows.Close()\n\tif err := queryRows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"iterate dependency targets: %w\", err)\n\t}\n\n\t//nolint:gosec // table and column are hardcoded by callers.\n\tif _, 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 {\n\t\treturn fmt.Errorf(\"delete old dependency target: %w\", err)\n\t}\n\tfor _, row := range rows {\n\t\t// The retargeted edge's natural key is (issue_id, newID): the switch above\n\t\t// set exactly one typed target column to newID. Re-derive id from it so the\n\t\t// rewritten row stays merge-safe and keeps a clone-stable primary key (#4259).\n\t\t//nolint:gosec // table is hardcoded by callers.\n\t\tif _, err := tx.ExecContext(ctx, fmt.Sprintf(`\n\t\t\tINSERT INTO %s (id, issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external, type, created_at, created_by, metadata, thread_id)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\t`, 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 {\n\t\t\treturn fmt.Errorf(\"insert replacement dependency target: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc nullStringValue(value sql.NullString) any {\n\tif !value.Valid {\n\t\treturn nil\n\t}\n\treturn value.String\n}\n\nfunc nullTimeValue(value sql.NullTime) any {\n\tif !value.Valid {\n\t\treturn nil\n\t}\n\treturn value.Time\n}","sourceCodeStart":743,"sourceCodeEnd":779,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L743-L779","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: both issues depend on bd-200; retarget inserts a duplicate id\nINSERT INTO dependencies (id, issue_id, depends_on_issue_id, ...) VALUES ('bd-100->bd-200', 'bd-100', 'bd-200', ...);\n-- Error 1062: Duplicate entry 'bd-100->bd-200'\n// after: drop the pre-existing edge first\nDELETE FROM dependencies WHERE issue_id='bd-100' AND depends_on_issue_id='bd-200';\n-- then retry the rename so the reinsert succeeds","handlingStrategy":"validation","validationCode":"// Ensure no edge already exists under the post-rename deterministic id\nvar dupes int\ndb.Get(&dupes, `SELECT COUNT(*) FROM dependencies d\n  WHERE d.issue_id IN (SELECT depends_on_issue_id FROM dependencies WHERE depends_on_issue_id = ?)\n    AND d.id = CONCAT(d.issue_id, '->', ?)`, oldID, newID)\n_ = dupes // or, simply: dedupe edges to the shared target before renaming\n// Check one-target invariant on rows being moved\nvar bad int\ndb.Get(&bad, `SELECT COUNT(*) FROM dependencies WHERE depends_on_issue_id = ?\n  AND (depends_on_wisp_id IS NOT NULL OR depends_on_external IS NOT NULL)`, oldID)\nif bad > 0 { return fmt.Errorf(\"%d rows violate one-target check; repair before rename\", bad) }","typeGuard":null,"tryCatchPattern":"err := updateIssueOrWispID(tx, oldID, newID)\nif err != nil {\n    var dup *mysql.MySQLError\n    if errors.As(err, &dup) && dup.Number == 1062 {\n        return fmt.Errorf(\"edge already exists after retarget (%s); dedupe and retry: %w\", dup.Message, err)\n    }\n    return err\n}","preventionTips":["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"],"tags":["database","insert","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"}