gastownhall/beads · error

db: LabelSQLRepository.DeleteAllForIDs from %s: %w

Error message

db: LabelSQLRepository.DeleteAllForIDs from %s: %w

What it means

LabelSQLRepository.DeleteAllForIDs failed to execute its DELETE for one of the target label tables. The repository returns the running total alongside the wrapped driver error. Note the wisps-table 'table not exist' case is treated as success, so this error is a real DELETE failure for a table that does exist (or a non-wisps table).

Source

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

		if end > len(ids) {
			end = len(ids)
		}
		batch := ids[start:end]
		placeholders := make([]string, len(batch))
		args := make([]any, len(batch))
		for i, id := range batch {
			placeholders[i] = "?"
			args[i] = id
		}
		//nolint:gosec // G201: table is one of two hardcoded constants; ? placeholders only.
		res, err := r.runner.ExecContext(ctx,
			fmt.Sprintf("DELETE FROM %s WHERE issue_id IN (%s)", table, strings.Join(placeholders, ",")),
			args...)
		if err != nil {
			if opts.UseWispsTable && dberrors.IsTableNotExist(err) {
				return total, nil
			}
			return total, fmt.Errorf("db: LabelSQLRepository.DeleteAllForIDs from %s: %w", table, err)
		}
		n, err := res.RowsAffected()
		if err != nil {
			return total, fmt.Errorf("db: LabelSQLRepository.DeleteAllForIDs rows affected: %w", err)
		}
		total += int(n)
	}
	return total, nil
}

func (r *labelSQLRepositoryImpl) CountAllForIDs(ctx context.Context, ids []string, opts domain.LabelOpts) (int, error) {
	if len(ids) == 0 {
		return 0, nil
	}
	table := "labels"
	if opts.UseWispsTable {
		table = "wisp_labels"
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error to see the driver cause (no such table, locked, too many params)
  2. Run schema migrations so the labels table exists before deleting
  3. Batch the ids slice if the IN-clause exceeds driver parameter limits
  4. Check for concurrent writers/locks on the database and retry when idle

Example fix

// before
n, err := repo.DeleteAllForIDs(ctx, allIDs, opts)
// after
for i := 0; i < len(allIDs); i += 500 {
    if _, err := repo.DeleteAllForIDs(ctx, allIDs[i:min(i+500, len(allIDs))], opts); err != nil { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// ensure table exists and batch ids
if len(ids) == 0 { return 0, nil }
for start := 0; start < len(ids); start += 500 { _ = ids[start:min(start+500, len(ids))] }

Try / catch

n, err := repo.DeleteAllForIDs(ctx, ids, opts)
if err != nil {
    if dberrors.IsTableNotExist(errors.Unwrap(err)) && opts.UseWispsTable {
        return 0, nil // treat as no-op
    }
    return err
}

Prevention

When it happens

Trigger: Calling DeleteAllForIDs when the DELETE statement fails: table missing in non-wisps mode, SQL error, connection failure, or lock conflict.

Common situations: Schema migrations not run so the labels table is absent; database locked by another writer; too many IDs generating too many placeholders; storage file corruption in embedded mode.

Related errors


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