{"record":{"id":"b27e0fc2c25ce967","repo":"gastownhall/beads","slug":"remove-label-w","errorCode":null,"errorMessage":"remove label: %w","messagePattern":"remove label: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/labels.go","lineNumber":186,"sourceCode":"\n// RemoveLabelInTx removes a label from an issue and records an event within\n// an existing transaction. Automatically routes to wisp tables if the ID is\n// an active wisp.\n//\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc RemoveLabelInTx(ctx context.Context, tx DBTX, labelTable, eventTable, issueID, label, actor string) error {\n\tif labelTable == \"\" || eventTable == \"\" {\n\t\tisWisp := IsActiveWispInTx(ctx, tx, issueID)\n\t\t_, lt, et, _ := WispTableRouting(isWisp)\n\t\tif labelTable == \"\" {\n\t\t\tlabelTable = lt\n\t\t}\n\t\tif eventTable == \"\" {\n\t\t\teventTable = et\n\t\t}\n\t}\n\tif _, err := tx.ExecContext(ctx, fmt.Sprintf(`DELETE FROM %s WHERE issue_id = ? AND label = ?`, labelTable), issueID, label); err != nil {\n\t\treturn fmt.Errorf(\"remove label: %w\", err)\n\t}\n\tcomment := \"Removed label: \" + label\n\tif err := InsertDerivedEvent(ctx, tx, eventTable, AuxEvent{\n\t\tIssueID:   issueID,\n\t\tEventType: types.EventLabelRemoved,\n\t\tActor:     actor,\n\t\tComment:   str(comment),\n\t}); err != nil {\n\t\treturn fmt.Errorf(\"remove label: record event: %w\", err)\n\t}\n\treturn RecordEventInTx(ctx, tx, EventUpdate, issueID, actor)\n}\n","sourceCodeStart":168,"sourceCodeEnd":199,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/labels.go#L168-L199","documentation":"RemoveLabelInTx returns this when the DELETE of a label row from labels/wisp_labels fails at execution time. Like the add path, removal is followed by a derived event and update journal entry, all within the caller's transaction. The error wraps the raw driver error so the remove-label step is identifiable in ApplyLabelPatch failure chains.","triggerScenarios":"ApplyLabelPatch → RemoveLabelInTx where the DELETE fails: label table missing, connection failure, or lock contention on the row inside a long transaction.","commonSituations":"Deadlocks/lock waits on busy beads under concurrent writers; schema versions missing wisp_labels; remote backend connection drops.","solutions":["Retry the transaction; DELETE failures here are often transient lock contention","Run schema migrations to ensure label tables exist","Inspect the wrapped error for deadlock/lock-wait messages and reduce transaction scope or retry with backoff"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// existence check is optional; removal is idempotent when the row is absent\nif err := ctx.Err(); err != nil { return fmt.Errorf(\"context done before label removal: %w\", err) }","typeGuard":null,"tryCatchPattern":"if err := issueops.RemoveLabelInTx(ctx, tx, issueID, label, actor); err != nil {\n    if strings.Contains(err.Error(), \"deadlock\") || strings.Contains(err.Error(), \"lock wait\") {\n        // transient contention: retry transaction with backoff\n    }\n    return err\n}","preventionTips":["Retry transactions on deadlock/lock-wait errors with backoff","Keep transactions short to reduce lock hold time","Ensure schema migrations created the label tables","Serialize label mutations for the same issue in high-contention workflows"],"tags":["database","sql","labels","delete"],"backgroundTag":"sql-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}