gastownhall/beads · error

set labels: remove

Error message

set labels: remove %s: %w

What it means

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.

Solutions

  1. Read the wrapped error and the named label to identify the failing delete
  2. Re-run SetLabels idempotently — it recomputes the diff, so a retry completes remaining removals
  3. Check for concurrent writers (locks, other agents) and serialize label mutations
  4. Verify the ID/table type matches (issue vs wisp APIs)

Example fix

// before
if err := store.SetLabels(ctx, id, desired, actor); err != nil {
	log.Fatal(err) // gives up on partial state
}
// after
if err := store.SetLabels(ctx, id, desired, actor); err != nil {
	time.Sleep(backoff)
	return store.SetLabels(ctx, id, desired, actor) // recomputes diff
}
Defensive patterns

Strategy: retry

Validate before calling

current, err := store.GetLabels(ctx, id)
if err != nil { return err }
// Pre-check that removals are actually needed
if len(current) == 0 { return nil }

Try / catch

if err := store.SetLabels(ctx, id, desired, actor); err != nil {
	log.Printf("set labels partially applied on %s: %v — retrying", id, err)
	time.Sleep(backoff)
	return store.SetLabels(ctx, id, desired, actor) // recomputes diff
}

Prevention

When it happens

Trigger: SetLabels where the desired set omits existing labels and Delete of one of them fails: driver error, concurrent modification, missing record, constraint.

Common situations: Two agents running SetLabels concurrently on the same issue; connectivity drop mid-diff-apply; wisp/issue table mismatch causing delete against the wrong table.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/9ef96616ba7ef889. Report an issue: GitHub.

Appendix: source

Thrown at internal/storage/domain/label.go:169

		return fmt.Errorf("set labels: id must not be empty")
	}
	opts := LabelOpts{UseWispsTable: useWisp}
	current, err := u.labelRepo.List(ctx, id, opts)
	if err != nil {
		return fmt.Errorf("set labels: list current: %w", err)
	}
	desired := make(map[string]bool, len(labels))
	for _, l := range labels {
		if l != "" {
			desired[l] = true
		}
	}
	existing := make(map[string]bool, len(current))
	for _, l := range current {
		existing[l] = true
		if !desired[l] {
			if err := u.labelRepo.Delete(ctx, id, l, actor, opts); err != nil {
				return fmt.Errorf("set labels: remove %s: %w", l, err)
			}
		}
	}
	for l := range desired {
		if !existing[l] {
			if err := u.labelRepo.Insert(ctx, id, l, actor, opts); err != nil {
				return fmt.Errorf("set labels: add %s: %w", l, err)
			}
		}
	}
	return nil
}

func (u *labelUseCaseImpl) GetLabels(ctx context.Context, issueID string) ([]string, error) {
	return u.list(ctx, issueID, false)
}

func (u *labelUseCaseImpl) GetWispLabels(ctx context.Context, wispID string) ([]string, error) {

View on GitHub (pinned to 71377f2769)