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
- Verify the issue/wisp was actually created and its ID captured before calling AddLabels
- Log or print the ID immediately before the call to confirm it is non-empty
- Fix the upstream code path that produced the empty ID (failed lookup, missing CLI argument, unset struct field)
- 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
- Always use the ID returned by CreateIssue/GetIssue, never a hand-typed placeholder
- Validate IDs at system boundaries (CLI args, JSON decode) before business calls
- In bulk jobs, skip records with empty IDs and log them
- Distinguish issue IDs from wisp IDs and use the matching API
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
- remove labels: id must not be empty
- set labels: id must not be empty
- get labels: id must not be empty
- db: Exists: id must not be empty
- ErrFieldTooLong
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6adfb9c82964cc5d.
Report an issue: GitHub.