gastownhall/beads · error
db: LabelSQLRepository.Insert: label must not be empty
Error message
db: LabelSQLRepository.Insert: label must not be empty
What it means
Input validation error from LabelSQLRepository.Insert: the label argument is empty. The repository rejects empty labels before INSERT IGNORE, because the database would otherwise silently store an empty/blank label row. This mirrors the issueops.AddLabelInTx guard so both write stacks behave identically.
Source
Thrown at internal/storage/domain/db/label.go:40
runner Runner
events domain.EventsSQLRepository
}
var _ domain.LabelSQLRepository = (*labelSQLRepositoryImpl)(nil)
func pickLabelTable(useWisps bool) string {
if useWisps {
return "wisp_labels"
}
return "labels"
}
func (r *labelSQLRepositoryImpl) Insert(ctx context.Context, issueID, label, actor string, opts domain.LabelOpts) error {
if issueID == "" {
return fmt.Errorf("db: LabelSQLRepository.Insert: issueID must not be empty")
}
if label == "" {
return fmt.Errorf("db: LabelSQLRepository.Insert: label must not be empty")
}
// Reject an over-length label before the INSERT IGNORE, which would otherwise
// silently truncate it to the VARCHAR(255) column. This is the proxied-server
// (uow) analog of issueops.AddLabelInTx's guard, so both write stacks return a
// typed ErrFieldTooLong instead of storing a label the caller never sent.
if err := types.CheckFieldLen("label", label); err != nil {
return err
}
table := pickLabelTable(opts.UseWispsTable)
//nolint:gosec // G201: table is one of two hardcoded constants
result, err := r.runner.ExecContext(ctx,
fmt.Sprintf("INSERT IGNORE INTO %s (issue_id, label) VALUES (?, ?)", table),
issueID, label,
)
if err != nil {
return fmt.Errorf("db: LabelSQLRepository.Insert %s/%s: %w", issueID, label, err)
}
rows, err := result.RowsAffected()View on GitHub (pinned to 71377f2769)
Solutions
- Validate the label is non-empty (after trimming) before calling Insert
- Reject empty label at the API/CLI layer with a clear message
- Check upstream parsing for fields dropped or trimmed to empty
- Also mind CheckFieldLen: labels over 255 chars are rejected as typed ErrFieldTooLong
Example fix
// before
label := strings.TrimSpace(input.Label)
repo.Insert(ctx, id, label, actor, opts)
// after
label := strings.TrimSpace(input.Label)
if label == "" {
return fmt.Errorf("label must not be empty")
}
repo.Insert(ctx, id, label, actor, opts) Defensive patterns
Strategy: validation
Validate before calling
label := strings.TrimSpace(userLabel)
if label == "" || len(label) > 255 {
return fmt.Errorf("label must be 1-255 chars")
} Type guard
func validLabel(s string) bool {
t := strings.TrimSpace(s)
return t != "" && len(t) <= 255
} Try / catch
if err := repo.Insert(ctx, issueID, label, actor, opts); err != nil {
var tooLong *types.ErrFieldTooLong
if strings.Contains(err.Error(), "label must not be empty") || errors.As(err, &tooLong) {
return fmt.Errorf("invalid label %q: %w", label, err)
}
return err
} Prevention
- Trim and validate labels before Insert
- Reject empty/overlong labels at the CLI and API layers
- Mirror the issueops.AddLabelInTx guards in any custom write path
- Test label endpoints with empty and 256+ char inputs
When it happens
Trigger: Calling Insert(ctx, issueID, "", actor, opts) — e.g. a label string trimmed to nothing, a missing field in a JSON payload, or whitespace-only input.
Common situations: Client sending "label": "" in an API request, strings.TrimLeft wiping a whitespace label, or a template/config defaulting the label to empty.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- db: LabelSQLRepository.Insert: issueID must not be empty
- add label: id must not be empty
- add label: label must not be empty
- remove label: id must not be empty
- remove label: label must not be empty
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/29125110d19f69d7.
Report an issue: GitHub.