gastownhall/beads · error

db: LabelSQLRepository.Delete %s/%s: %w

Error message

db: LabelSQLRepository.Delete %s/%s: %w

What it means

Wraps any failure of the DELETE FROM statement in LabelSQLRepository.Delete, adding issue ID and label context. Thrown when the SQL driver rejects the delete (connection failure, schema problem, permissions), so the label removal did not happen and no event was journaled.

Source

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

	// returns without writing and journals nothing.
	return issueops.RecordEventInTx(ctx, r.runner, issueops.EventUpdate, issueID, actor)
}

func (r *labelSQLRepositoryImpl) Delete(ctx context.Context, issueID, label, actor string, opts domain.LabelOpts) error {
	if issueID == "" {
		return fmt.Errorf("db: LabelSQLRepository.Delete: issueID must not be empty")
	}
	if label == "" {
		return fmt.Errorf("db: LabelSQLRepository.Delete: label must not be empty")
	}
	table := pickLabelTable(opts.UseWispsTable)
	//nolint:gosec // G201: table is one of two hardcoded constants
	result, err := r.runner.ExecContext(ctx,
		fmt.Sprintf("DELETE FROM %s WHERE issue_id = ? AND label = ?", table),
		issueID, label,
	)
	if err != nil {
		return fmt.Errorf("db: LabelSQLRepository.Delete %s/%s: %w", issueID, label, err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("db: LabelSQLRepository.Delete %s/%s: rows affected: %w", issueID, label, err)
	}
	if rows == 0 {
		return nil
	}
	if err := r.events.Record(ctx, domain.Event{
		IssueID:  issueID,
		Type:     types.EventLabelRemoved,
		Actor:    actor,
		OldValue: label,
	}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {
		return err
	}
	return issueops.RecordEventInTx(ctx, r.runner, issueops.EventUpdate, issueID, actor)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped driver error via errors.As
  2. Verify the labels table exists and is writable
  3. Check DB connectivity and credentials/privileges
  4. Retry if the failure was transient

Example fix

// before
repo.Delete(ctx, issueID, label, actor, opts)
// after
if err := repo.Delete(ctx, issueID, label, actor, opts); err != nil {
    var drv *mysql.MySQLError
    if errors.As(err, &drv) { log.Errorf("delete failed: %d %v", drv.Number, drv) }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if issueID == "" || label == "" { return fmt.Errorf("issueID and label required") }
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("db unavailable: %w", err) }

Type guard

func isSQLErr(err error) bool { var e *mysql.MySQLError; return errors.As(err, &e) }

Try / catch

if err := repo.Delete(ctx, issueID, label, actor, opts); err != nil {
    var sqle *mysql.MySQLError
    if errors.As(err, &sqle) { log.Errorf("delete failed: %d %s", sqle.Number, sqle.Message) }
    return fmt.Errorf("remove label %q from %s: %w", label, issueID, err)
}

Prevention

When it happens

Trigger: Calling Delete when the DB connection is broken, the labels table is missing, or the user lacks DELETE privileges on the table.

Common situations: Database not migrated (labels table absent); read-only replica receiving writes; connection dropped mid-transaction.

Related errors


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