{"record":{"id":"b50037ec2129351c","repo":"gastownhall/beads","slug":"delete-old-dependency-target-w","errorCode":null,"errorMessage":"delete old dependency target: %w","messagePattern":"delete old dependency target: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":750,"sourceCode":"\t\t\trow.external = sql.NullString{}\n\t\tcase \"depends_on_wisp_id\":\n\t\t\trow.issueTarget = sql.NullString{}\n\t\t\trow.wispTarget = sql.NullString{String: newID, Valid: true}\n\t\t\trow.external = sql.NullString{}\n\t\tdefault:\n\t\t\t_ = queryRows.Close()\n\t\t\treturn fmt.Errorf(\"replace dependency target: unsupported typed column %q\", column)\n\t\t}\n\t\trows = append(rows, row)\n\t}\n\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 {","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L732-L768","documentation":"This error wraps failure of the DELETE that removes old-target rows in replaceDependencyTargetInTx, just before reinserting them with the new target. It means the `DELETE FROM <table> WHERE <column> = ? OR (<DepTargetExpr> = ? AND depends_on_external IS NULL)` statement failed at the driver/schema level (not a duplicate-key problem — this is a delete).","triggerScenarios":"UpdateIssueIDInTx / UpdateWispIDInTx rename, when the DELETE on dependencies or wisp_dependencies fails: FK restriction from a child table, aborted transaction, dropped connection, or lock wait timeout with concurrent writers.","commonSituations":"Concurrent bd processes holding row locks on the same dependency rows; FK constraints from other tables referencing the old rows; connection loss during a bulk rename.","solutions":["Read the wrapped driver error: lock wait timeout => retry when the other writer finishes; FK violation => remove or update the referencing rows first","Retry the rename — the transaction rolls back the read rows so nothing is lost","Run renames serially; avoid two bd processes renaming overlapping issues","Check for long-running transactions holding locks on dependencies rows"],"exampleFix":"// before: concurrent writer blocks the delete\nDELETE FROM dependencies WHERE depends_on_issue_id = 'bd-100' ...\n-- Error 1205: Lock wait timeout exceeded\n// after: ensure no other bd process is mid-operation, then retry\nbd doctor  # verify no stale locks/transactions\nbd update bd-101 --id bd-100","handlingStrategy":"retry","validationCode":"// Detect blocking locks or dependent FK rows before the rename\nvar locks int\ndb.Get(&locks, `SELECT COUNT(*) FROM information_schema.INNODB_TRX WHERE TRX_STARTED < NOW() - INTERVAL 30 SECOND`)\nif locks > 0 { return fmt.Errorf(\"%d long-running transactions may block the delete; retry later\", locks) }","typeGuard":null,"tryCatchPattern":"err := updateIssueID(tx, oldID, newID)\nif err != nil {\n    var my *mysql.MySQLError\n    if errors.As(err, &my) && (my.Number == 1205 || my.Number == 1213) {\n        // lock wait timeout / deadlock: wait and retry the whole rename\n    }\n    return err\n}","preventionTips":["Run only one bd mutating process per database at a time","Keep transactions short so they don't hold dependency-row locks","Set a sane innodb_lock_wait_timeout and retry whole renames on timeout","Check FK references before deleting/renaming heavily-linked issues"],"tags":["database","delete","transaction","lock"],"backgroundTag":"sql-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}