gastownhall/beads · error

affected by batch delete: %w

Error message

affected by batch delete: %w

What it means

DeleteResolvedSetInTx aborts when AffectedByDeletionInTx — which computes which remaining issues would become blocked or orphaned by this deletion — fails. The wrapped error comes from that blocked-state analysis query set. This runs in dry-run mode and before actual deletes, so nothing has been removed yet when it fires.

Source

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

			_ = rows.Close()
			if err := rows.Err(); err != nil {
				return nil, fmt.Errorf("iterate inbound dependencies from %s: %w", depTable, err)
			}
		}
	}

	result.DependenciesCount = depsCount
	result.LabelsCount = labelsCount
	result.EventsCount = eventsCount
	result.DeletedCount = len(set.RegularIDs) + len(set.WispIDs)

	if dryRun {
		return result, nil
	}

	affectedIssues, affectedWisps, aerr := AffectedByDeletionInTx(ctx, tx, set.RegularIDs, set.WispIDs)
	if aerr != nil {
		return nil, fmt.Errorf("affected by batch delete: %w", aerr)
	}

	// Resolve WHICH regular ids this delete actually removes before the batched
	// DELETE runs: afterwards the rows are gone, and RowsAffected reports a
	// count, not a set. A journal record for an id that was already absent would
	// tell a consumer to drop a bead this transaction never touched.
	journaledDeletes, err := journalableDeletesInTx(ctx, tx, "issues", set.RegularIDs)
	if err != nil {
		return nil, err
	}
	// Edges are journaled before the rows go, while their source snapshots can
	// still be read.
	if err := RecordDependencyRemovalsForIssuesInTx(ctx, tx, set.All); err != nil {
		return nil, fmt.Errorf("journal dependency removals for batch delete: %w", err)
	}

	for _, id := range set.WispIDs {
		if err := deleteIssueRowInTx(ctx, tx, id, true); err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error for the specific query that failed inside the analysis.
  2. Verify the backend implements the full driver interface including blocked-state analysis.
  3. Retry on a fresh transaction; safe because nothing was deleted yet.
  4. Run schema migrations if blocked-state tables are missing.

Example fix

// before
res, err := store.DeleteInTx(ctx, tx, ids)
// after
res, err := store.DeleteInTx(ctx, tx, ids)
if err != nil {
    return fmt.Errorf("no rows deleted (analysis failed first): %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// blocked-state analysis is part of the driver surface; verify support first
if !driver.Supports(ctx, driver.FeatureBlockedStateAnalysis) {
    return errors.New("backend does not support blocked-state analysis required for delete")
}

Try / catch

res, err := store.DeleteInTx(ctx, tx, ids)
if err != nil && strings.Contains(err.Error(), "affected by batch delete") {
    return fmt.Errorf("pre-delete analysis failed, nothing was deleted (safe to retry): %w", err)
}

Prevention

When it happens

Trigger: Calling DeleteIssuesInTx/DeleteInTx when the blocked-state analysis queries fail: missing blocked-state tables, connection failure, context cancellation, or a driver that can't execute the affected-set SQL.

Common situations: Alternative/embedded drivers lacking the SQL surface AffectedByDeletionInTx needs; schema drift removing blocked-state structures; timeouts on repos with huge dependency graphs.

Related errors


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