{"record":{"id":"8161c4e258f77910","repo":"gastownhall/beads","slug":"remove-labels-s-w","errorCode":null,"errorMessage":"remove labels: %s: %w","messagePattern":"remove labels: (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/label.go","lineNumber":135,"sourceCode":"func (u *labelUseCaseImpl) RemoveLabels(ctx context.Context, issueID string, labels []string, actor string) error {\n\treturn u.removeMany(ctx, issueID, labels, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) RemoveWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {\n\treturn u.removeMany(ctx, wispID, labels, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) removeMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"remove labels: id must not be empty\")\n\t}\n\topts := LabelOpts{UseWispsTable: useWisp}\n\tfor _, label := range labels {\n\t\tif label == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif err := u.labelRepo.Delete(ctx, id, label, actor, opts); err != nil {\n\t\t\treturn fmt.Errorf(\"remove labels: %s: %w\", label, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (u *labelUseCaseImpl) SetLabels(ctx context.Context, issueID string, labels []string, actor string) error {\n\treturn u.setMany(ctx, issueID, labels, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) SetWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {\n\treturn u.setMany(ctx, wispID, labels, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) setMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"set labels: id must not be empty\")\n\t}\n\topts := LabelOpts{UseWispsTable: useWisp}","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/label.go#L117-L153","documentation":"Wraps an error from labelRepo.Delete while removing one label in removeMany (RemoveLabels / RemoveWispLabels). The failing label is named in the message. Because removal proceeds label-by-label, earlier successful deletions are not rolled back, so the batch can end partially applied.","triggerScenarios":"RemoveLabels/RemoveWispLabels where labelRepo.Delete fails: storage driver error, record missing, constraint or permissions issue, or the underlying row was concurrently deleted.","commonSituations":"Concurrent agents mutating the same issue's labels; database connectivity drops mid-loop; removing labels from a nonexistent or cross-database ID; Dolt transaction conflicts.","solutions":["Inspect the wrapped error for the root cause (driver, constraint, not-found)","Re-list labels (GetLabels) to see the post-failure state and reconcile","Retry idempotently: Delete of an already-removed label can be treated as success by filtering labels from GetLabels output first","Check for concurrent writers if errors appear intermittently"],"exampleFix":"// before\nstore.RemoveLabels(ctx, id, labels, actor) // may partially fail\n// after\ncurrent, err := store.GetLabels(ctx, id)\nif err != nil { return err }\nvar want []string\nfor _, l := range labels {\n\tif slices.Contains(current, l) { want = append(want, l) }\n}\nreturn store.RemoveLabels(ctx, id, want, actor)","handlingStrategy":"try-catch","validationCode":"current, err := store.GetLabels(ctx, id)\nif err != nil { return err }\nwant := slices.DeleteFunc(slices.Clone(labels), func(l string) bool {\n\treturn !slices.Contains(current, l)\n})","typeGuard":null,"tryCatchPattern":"if err := store.RemoveLabels(ctx, id, labels, actor); err != nil {\n\tlog.Printf(\"label removal partially failed on %s: %v\", id, err)\n\t// reconcile\n\tcur, gerr := store.GetLabels(ctx, id)\n\tif gerr != nil { return gerr }\n\tlog.Printf(\"remaining labels: %v\", cur)\n\treturn err\n}","preventionTips":["Only request removal of labels that are actually present (list first)","Treat removal as idempotent and safe to retry","Serialize concurrent writers on the same issue","Reconcile state after partial batch failures"],"tags":["labels","storage","delete-failed"],"backgroundTag":"label-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}