{"record":{"id":"9ef96616ba7ef889","repo":"gastownhall/beads","slug":"set-labels-remove-s-w","errorCode":null,"errorMessage":"set labels: remove %s: %w","messagePattern":"set labels: remove (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/label.go","lineNumber":169,"sourceCode":"\t\treturn fmt.Errorf(\"set labels: id must not be empty\")\n\t}\n\topts := LabelOpts{UseWispsTable: useWisp}\n\tcurrent, err := u.labelRepo.List(ctx, id, opts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"set labels: list current: %w\", err)\n\t}\n\tdesired := make(map[string]bool, len(labels))\n\tfor _, l := range labels {\n\t\tif l != \"\" {\n\t\t\tdesired[l] = true\n\t\t}\n\t}\n\texisting := make(map[string]bool, len(current))\n\tfor _, l := range current {\n\t\texisting[l] = true\n\t\tif !desired[l] {\n\t\t\tif err := u.labelRepo.Delete(ctx, id, l, actor, opts); err != nil {\n\t\t\t\treturn fmt.Errorf(\"set labels: remove %s: %w\", l, err)\n\t\t\t}\n\t\t}\n\t}\n\tfor l := range desired {\n\t\tif !existing[l] {\n\t\t\tif err := u.labelRepo.Insert(ctx, id, l, actor, opts); err != nil {\n\t\t\t\treturn fmt.Errorf(\"set labels: add %s: %w\", l, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (u *labelUseCaseImpl) GetLabels(ctx context.Context, issueID string) ([]string, error) {\n\treturn u.list(ctx, issueID, false)\n}\n\nfunc (u *labelUseCaseImpl) GetWispLabels(ctx context.Context, wispID string) ([]string, error) {","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/label.go#L151-L187","documentation":"Wraps a failure from labelRepo.Delete during the removal half of setMany (SetLabels / SetWispLabels). When the current label set is being pruned toward the desired set, deleting a no-longer-wanted label failed. The offending label is named; changes made before the failure are not rolled back, so the label set may be partially updated.","triggerScenarios":"SetLabels where the desired set omits existing labels and Delete of one of them fails: driver error, concurrent modification, missing record, constraint.","commonSituations":"Two agents running SetLabels concurrently on the same issue; connectivity drop mid-diff-apply; wisp/issue table mismatch causing delete against the wrong table.","solutions":["Read the wrapped error and the named label to identify the failing delete","Re-run SetLabels idempotently — it recomputes the diff, so a retry completes remaining removals","Check for concurrent writers (locks, other agents) and serialize label mutations","Verify the ID/table type matches (issue vs wisp APIs)"],"exampleFix":"// before\nif err := store.SetLabels(ctx, id, desired, actor); err != nil {\n\tlog.Fatal(err) // gives up on partial state\n}\n// after\nif err := store.SetLabels(ctx, id, desired, actor); err != nil {\n\ttime.Sleep(backoff)\n\treturn store.SetLabels(ctx, id, desired, actor) // recomputes diff\n}","handlingStrategy":"retry","validationCode":"current, err := store.GetLabels(ctx, id)\nif err != nil { return err }\n// Pre-check that removals are actually needed\nif len(current) == 0 { return nil }","typeGuard":null,"tryCatchPattern":"if err := store.SetLabels(ctx, id, desired, actor); err != nil {\n\tlog.Printf(\"set labels partially applied on %s: %v — retrying\", id, err)\n\ttime.Sleep(backoff)\n\treturn store.SetLabels(ctx, id, desired, actor) // recomputes diff\n}","preventionTips":["Retry SetLabels on failure — the recomputed diff completes pending removals","Serialize concurrent writers on the same issue to avoid mid-diff conflicts","Check the named label in the message to diagnose which delete failed","Reconcile with GetLabels after persistent failures"],"tags":["labels","storage","delete-failed","partial-write"],"backgroundTag":"label-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}