{"record":{"id":"316ab6cfbd7aacef","repo":"gastownhall/beads","slug":"db-labelsqlrepository-delete-s-s-w","errorCode":null,"errorMessage":"db: LabelSQLRepository.Delete %s/%s: %w","messagePattern":"db: LabelSQLRepository\\.Delete (.+?)/(.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/label.go","lineNumber":104,"sourceCode":"\t// returns without writing and journals nothing.\n\treturn issueops.RecordEventInTx(ctx, r.runner, issueops.EventUpdate, issueID, actor)\n}\n\nfunc (r *labelSQLRepositoryImpl) Delete(ctx context.Context, issueID, label, actor string, opts domain.LabelOpts) error {\n\tif issueID == \"\" {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete: issueID must not be empty\")\n\t}\n\tif label == \"\" {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete: label must not be empty\")\n\t}\n\ttable := pickLabelTable(opts.UseWispsTable)\n\t//nolint:gosec // G201: table is one of two hardcoded constants\n\tresult, err := r.runner.ExecContext(ctx,\n\t\tfmt.Sprintf(\"DELETE FROM %s WHERE issue_id = ? AND label = ?\", table),\n\t\tissueID, label,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete %s/%s: %w\", issueID, label, err)\n\t}\n\trows, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete %s/%s: rows affected: %w\", issueID, label, err)\n\t}\n\tif rows == 0 {\n\t\treturn nil\n\t}\n\tif err := r.events.Record(ctx, domain.Event{\n\t\tIssueID:  issueID,\n\t\tType:     types.EventLabelRemoved,\n\t\tActor:    actor,\n\t\tOldValue: label,\n\t}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {\n\t\treturn err\n\t}\n\treturn issueops.RecordEventInTx(ctx, r.runner, issueops.EventUpdate, issueID, actor)\n}","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/label.go#L86-L122","documentation":"Wraps any failure of the DELETE FROM statement in LabelSQLRepository.Delete, adding issue ID and label context. Thrown when the SQL driver rejects the delete (connection failure, schema problem, permissions), so the label removal did not happen and no event was journaled.","triggerScenarios":"Calling Delete when the DB connection is broken, the labels table is missing, or the user lacks DELETE privileges on the table.","commonSituations":"Database not migrated (labels table absent); read-only replica receiving writes; connection dropped mid-transaction.","solutions":["Inspect the wrapped driver error via errors.As","Verify the labels table exists and is writable","Check DB connectivity and credentials/privileges","Retry if the failure was transient"],"exampleFix":"// before\nrepo.Delete(ctx, issueID, label, actor, opts)\n// after\nif err := repo.Delete(ctx, issueID, label, actor, opts); err != nil {\n    var drv *mysql.MySQLError\n    if errors.As(err, &drv) { log.Errorf(\"delete failed: %d %v\", drv.Number, drv) }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"if issueID == \"\" || label == \"\" { return fmt.Errorf(\"issueID and label required\") }\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"db unavailable: %w\", err) }","typeGuard":"func isSQLErr(err error) bool { var e *mysql.MySQLError; return errors.As(err, &e) }","tryCatchPattern":"if err := repo.Delete(ctx, issueID, label, actor, opts); err != nil {\n    var sqle *mysql.MySQLError\n    if errors.As(err, &sqle) { log.Errorf(\"delete failed: %d %s\", sqle.Number, sqle.Message) }\n    return fmt.Errorf(\"remove label %q from %s: %w\", label, issueID, err)\n}","preventionTips":["Ping DB before write operations","Ensure migrations created the labels tables","Grant DELETE privileges to the app DB user","Wrap context with a sane timeout"],"tags":["database","sql","delete"],"backgroundTag":"sql-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}