gastownhall/beads · error

add labels: id must not be empty

Error message

add labels: id must not be empty

What it means

Thrown by the labels use case when addMany (AddLabels / AddWispLabels) is called with an empty issue or wisp ID. The use case validates the ID up front because the repository layer cannot insert labels for a nonexistent or unidentifiable record. It is a fail-fast input validation guard, not a storage failure.

Source

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

		return fmt.Errorf("remove label: label must not be empty")
	}
	if err := u.labelRepo.Delete(ctx, id, label, actor, LabelOpts{UseWispsTable: useWisp}); err != nil {
		return fmt.Errorf("remove label %s/%s: %w", id, label, err)
	}
	return nil
}

func (u *labelUseCaseImpl) AddLabels(ctx context.Context, issueID string, labels []string, actor string) error {
	return u.addMany(ctx, issueID, labels, actor, false)
}

func (u *labelUseCaseImpl) AddWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {
	return u.addMany(ctx, wispID, labels, actor, true)
}

func (u *labelUseCaseImpl) addMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {
	if id == "" {
		return fmt.Errorf("add labels: id must not be empty")
	}
	opts := LabelOpts{UseWispsTable: useWisp}
	for _, label := range labels {
		if label == "" {
			continue
		}
		if err := u.labelRepo.Insert(ctx, id, label, actor, opts); err != nil {
			return fmt.Errorf("add labels: %s: %w", label, err)
		}
	}
	return nil
}

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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify the issue/wisp was actually created and its ID captured before calling AddLabels
  2. Log or print the ID immediately before the call to confirm it is non-empty
  3. Fix the upstream code path that produced the empty ID (failed lookup, missing CLI argument, unset struct field)
  4. If acting on a wisp, call AddWispLabels with the wisp ID rather than the issue ID

Example fix

// before
issue, _ := store.CreateIssue(ctx, opts)
err := store.AddLabels(ctx, "", labels, actor) // empty literal
// after
issue, err := store.CreateIssue(ctx, opts)
if err != nil { return err }
if issue.ID == "" { return fmt.Errorf("no issue id") }
err = store.AddLabels(ctx, issue.ID, labels, actor)
Defensive patterns

Strategy: validation

Validate before calling

if id == "" {
	return fmt.Errorf("cannot add labels: issue id is empty")
}
if err := store.AddLabels(ctx, id, labels, actor); err != nil { ... }

Prevention

When it happens

Trigger: Calling AddLabels(ctx, "", labels, actor) or AddWispLabels(ctx, "", labels, actor), or passing an ID variable that was never populated (empty string from a failed lookup, unset config field, or empty struct field).

Common situations: Parsing an issue key from user input that trimmed to empty; reading an ID from an unmarshaled config/JSON that omitted the field; constructing issue structs where the ID is assigned after labels are set; wiring commands where the positional argument was missing.

Related errors


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