gastownhall/beads · error

db: LabelSQLRepository.Insert: issueID must not be empty

Error message

db: LabelSQLRepository.Insert: issueID must not be empty

What it means

Input validation error from LabelSQLRepository.Insert: the issueID argument is the empty string. The SQL-layer repository refuses to issue an INSERT for a label without knowing which issue it attaches to, failing fast before touching the database.

Source

Thrown at internal/storage/domain/db/label.go:37

}

type labelSQLRepositoryImpl struct {
	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure the issue is created and its ID populated before calling Insert
  2. Fix the caller to pass the real issue ID
  3. Validate issueID non-empty at your API boundary
  4. Check for ID propagation bugs in uow/proxy layers

Example fix

// before
repo.Insert(ctx, issue.ID, "bug", actor, opts) // issue.ID == ""
// after
if issue.ID == "" {
    return fmt.Errorf("issue not persisted")
}
repo.Insert(ctx, issue.ID, "bug", actor, opts)
Defensive patterns

Strategy: validation

Validate before calling

func validateLabelInput(issueID, label string) error {
    if issueID == "" { return fmt.Errorf("issueID required") }
    if label == "" { return fmt.Errorf("label required") }
    return nil
}

Type guard

func hasIssueID(i *types.Issue) bool { return i != nil && i.ID != "" }

Try / catch

if err := repo.Insert(ctx, issueID, label, actor, opts); err != nil {
    if strings.Contains(err.Error(), "issueID must not be empty") {
        return fmt.Errorf("cannot label unpersisted issue %q", issueID)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Insert(ctx, "", label, actor, opts) directly, or via a proxy/uow write stack where the caller's issue was never assigned an ID (e.g. added before creation/flush).

Common situations: Adding a label to a not-yet-persisted issue, an ID field lost in a struct copy, or a proxied-server request missing the issue identifier.

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


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