gastownhall/beads · error
remove labels: %s: %w
Error message
remove labels: %s: %w
What it means
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.
Source
Thrown at internal/storage/domain/label.go:135
func (u *labelUseCaseImpl) RemoveLabels(ctx context.Context, issueID string, labels []string, actor string) error {
return u.removeMany(ctx, issueID, labels, actor, false)
}
func (u *labelUseCaseImpl) RemoveWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {
return u.removeMany(ctx, wispID, labels, actor, true)
}
func (u *labelUseCaseImpl) removeMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {
if id == "" {
return fmt.Errorf("remove labels: id must not be empty")
}
opts := LabelOpts{UseWispsTable: useWisp}
for _, label := range labels {
if label == "" {
continue
}
if err := u.labelRepo.Delete(ctx, id, label, actor, opts); err != nil {
return fmt.Errorf("remove labels: %s: %w", label, err)
}
}
return nil
}
func (u *labelUseCaseImpl) SetLabels(ctx context.Context, issueID string, labels []string, actor string) error {
return u.setMany(ctx, issueID, labels, actor, false)
}
func (u *labelUseCaseImpl) SetWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {
return u.setMany(ctx, wispID, labels, actor, true)
}
func (u *labelUseCaseImpl) setMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {
if id == "" {
return fmt.Errorf("set labels: id must not be empty")
}
opts := LabelOpts{UseWispsTable: useWisp}View on GitHub (pinned to 71377f2769)
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
Example fix
// before
store.RemoveLabels(ctx, id, labels, actor) // may partially fail
// after
current, err := store.GetLabels(ctx, id)
if err != nil { return err }
var want []string
for _, l := range labels {
if slices.Contains(current, l) { want = append(want, l) }
}
return store.RemoveLabels(ctx, id, want, actor) Defensive patterns
Strategy: try-catch
Validate before calling
current, err := store.GetLabels(ctx, id)
if err != nil { return err }
want := slices.DeleteFunc(slices.Clone(labels), func(l string) bool {
return !slices.Contains(current, l)
}) Try / catch
if err := store.RemoveLabels(ctx, id, labels, actor); err != nil {
log.Printf("label removal partially failed on %s: %v", id, err)
// reconcile
cur, gerr := store.GetLabels(ctx, id)
if gerr != nil { return gerr }
log.Printf("remaining labels: %v", cur)
return err
} Prevention
- 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
When it happens
Trigger: RemoveLabels/RemoveWispLabels where labelRepo.Delete fails: storage driver error, record missing, constraint or permissions issue, or the underlying row was concurrently deleted.
Common situations: 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.
Related errors
- set labels: remove %s: %w
- add label '%s' on %s: %w
- add label '%s' on %s: %w
- read labels for %s: %w
- copy label %q for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8161c4e258f77910.
Report an issue: GitHub.