gastownhall/beads · error

count events: %w

Error message

count events: %w

What it means

DeleteResolvedSetInTx aborts when the SELECT COUNT over the "events" table for regular issue IDs fails. The error wraps the driver error from CountRowsForIssueIDsInTx; the count populates the events figure of the dry-run delete result.

Source

Thrown at internal/storage/issueops/delete.go:231

		return nil, fmt.Errorf("count dependencies: %w", err)
	}
	wispDepsCount, err := CountRowsForIssueIDsInTx(ctx, tx, "wisp_dependencies", set.WispIDs)
	if err != nil {
		return nil, fmt.Errorf("count wisp dependencies: %w", err)
	}
	depsCount += wispDepsCount

	if labelsCount, err = CountRowsForIssueIDsInTx(ctx, tx, "labels", set.RegularIDs); err != nil {
		return nil, fmt.Errorf("count labels: %w", err)
	}
	wispLabelsCount, err := CountRowsForIssueIDsInTx(ctx, tx, "wisp_labels", set.WispIDs)
	if err != nil {
		return nil, fmt.Errorf("count wisp labels: %w", err)
	}
	labelsCount += wispLabelsCount

	if eventsCount, err = CountRowsForIssueIDsInTx(ctx, tx, "events", set.RegularIDs); err != nil {
		return nil, fmt.Errorf("count events: %w", err)
	}
	wispEventsCount, err := CountRowsForIssueIDsInTx(ctx, tx, "wisp_events", set.WispIDs)
	if err != nil {
		return nil, fmt.Errorf("count wisp events: %w", err)
	}
	eventsCount += wispEventsCount

	for i := 0; i < len(set.All); i += deleteBatchSize {
		end := i + deleteBatchSize
		if end > len(set.All) {
			end = len(set.All)
		}
		batch := set.All[i:end]
		batchInClause, batchArgs := buildSQLInClause(batch)

		for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
			rows, err := tx.QueryContext(ctx,
				fmt.Sprintf(`SELECT issue_id FROM %s WHERE %s`, depTable, depTargetIn("", batchInClause)),

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error for the underlying cause (timeout vs connection vs schema).
  2. Retry the delete; transient lock/timeout failures usually clear.
  3. Raise the context timeout for deletes touching many issues with many events.
  4. Verify the events table exists and is healthy (e.g. via bd doctor).

Example fix

// before
res, err := store.DeleteInTx(ctx, tx, ids)
if err != nil {
    log.Fatal(err)
}
// after
res, err := store.DeleteInTx(ctx, tx, ids)
if err != nil {
    log.Printf("delete failed: %v", err)
    if isTransient(err) {
        res, err = store.DeleteInTx(ctx, tx, ids) // retry
    }
}
Defensive patterns

Strategy: retry

Validate before calling

var n int
err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM events LIMIT 1`).Scan(&n)
if err != nil {
    return fmt.Errorf("events table unreadable before delete: %w", err)
}

Try / catch

res, err := store.DeleteIssuesInTx(ctx, tx, ids)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        ctx, cancel = context.WithTimeout(context.Background(), longerTimeout)
        defer cancel()
        res, err = store.DeleteIssuesInTx(ctx, tx, ids)
    }
}

Prevention

When it happens

Trigger: Calling DeleteIssuesInTx/DeleteInTx when events is missing/unreadable, the transaction connection dropped after the label counts, or ctx was cancelled before this step.

Common situations: Very large events tables causing count timeouts; connection reset by Dolt server; lock contention with concurrent writers on events.

Related errors


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