{"record":{"id":"0b82c5cfa6250965","repo":"gastownhall/beads","slug":"remove-label-s-s-w","errorCode":null,"errorMessage":"remove label %s/%s: %w","messagePattern":"remove label (.+?)/(.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/label.go","lineNumber":88,"sourceCode":"}\n\nfunc (u *labelUseCaseImpl) RemoveLabel(ctx context.Context, issueID, label, actor string) error {\n\treturn u.remove(ctx, issueID, label, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) RemoveWispLabel(ctx context.Context, wispID, label, actor string) error {\n\treturn u.remove(ctx, wispID, label, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) remove(ctx context.Context, id, label, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"remove label: id must not be empty\")\n\t}\n\tif label == \"\" {\n\t\treturn fmt.Errorf(\"remove label: label must not be empty\")\n\t}\n\tif err := u.labelRepo.Delete(ctx, id, label, actor, LabelOpts{UseWispsTable: useWisp}); err != nil {\n\t\treturn fmt.Errorf(\"remove label %s/%s: %w\", id, label, err)\n\t}\n\treturn nil\n}\n\nfunc (u *labelUseCaseImpl) AddLabels(ctx context.Context, issueID string, labels []string, actor string) error {\n\treturn u.addMany(ctx, issueID, labels, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) AddWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {\n\treturn u.addMany(ctx, wispID, labels, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) addMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"add labels: id must not be empty\")\n\t}\n\topts := LabelOpts{UseWispsTable: useWisp}\n\tfor _, label := range labels {","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/label.go#L70-L106","documentation":"Wraps an error returned by labelRepo.Delete when removing a label fails at the storage layer. The message embeds the issue ID and label (\"remove label %s/%s\") with the underlying cause via %w. It fires only after the empty-ID/empty-label guards pass.","triggerScenarios":"Calling RemoveLabel/RemoveWispLabel with valid arguments where Delete fails: the issue doesn't exist, the label row is absent, database locked/unavailable, or context cancellation.","commonSituations":"Removing a label that was already deleted (double-remove in a script); typo'd issue ID pointing at a nonexistent issue; concurrent bd processes contending for the store lock; dropped DB connection.","solutions":["Read the wrapped cause; treat 'not found'/'no rows' errors as success if idempotent removal is intended.","Verify the issue exists (bd show <id>) before removing labels.","Check for lock contention with other bd processes and retry when idle.","Retry with a fresh context for transient connection or cancellation errors.","Use RemoveLabels (batch form) when removing many labels so per-label failures are easier to handle."],"exampleFix":"// before\nerr := labelUC.RemoveLabel(ctx, id, label, actor) // fails when label already gone\n\n// after: idempotent remove\nerr := labelUC.RemoveLabel(ctx, id, label, actor)\nif err != nil && isNotFound(err) {\n    err = nil // label already absent\n}","handlingStrategy":"try-catch","validationCode":"// verify the issue exists and fetch current labels before removing\nlabels, err := uc.GetLabels(ctx, id)\nif err != nil { return err }\nif !slices.Contains(labels, label) { return nil // already absent — idempotent no-op }","typeGuard":"func isLabelRemoveErr(err error) bool {\n    return err != nil && strings.HasPrefix(err.Error(), \"remove label \")\n}","tryCatchPattern":"err := uc.RemoveLabel(ctx, id, label, actor)\nif err != nil {\n    cause := errors.Unwrap(err)\n    if isNotFound(cause) || isNoRows(cause) { return nil } // idempotent remove\n    return err\n}","preventionTips":["Check labels exist (GetLabels) before removing, for idempotency","Avoid double-remove in scripts by tracking which labels were already processed","Verify the issue ID exists before label operations","Retry transient storage errors; treat not-found as success where appropriate"],"tags":["storage","database","labels","delete"],"backgroundTag":"database-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}