gastownhall/beads · error

failed to get rows affected: %w

Error message

failed to get rows affected: %w

What it means

This error is returned when result.RowsAffected() — used to detect whether the close UPDATE actually matched a row — fails at the driver level. Beads relies on the affected-row count to distinguish 'closed' from 'issue not found / already closed', so a failure here aborts the close. The root driver error is preserved with %w.

Source

Thrown at internal/storage/issueops/close.go:350

	now := time.Now().UTC()

	// row_lock is rewritten on close so a concurrent reclaim (which also rewrites
	// row_lock) collides on this cell and is forced to conflict-and-retry rather
	// than silently cell-merging a revert-to-ready over a completed close (see
	// lease.go). The lease row is deleted below: a closed issue holds no lease.
	result, err := tx.ExecContext(ctx, fmt.Sprintf(`
		UPDATE %s SET status = ?, closed_at = ?, updated_at = ?, close_reason = ?, closed_by_session = ?,
			row_lock = ?
		WHERE id = ? AND status != ?
	`, issueTable), types.StatusClosed, now, now, reason, session, freshRowLock(), id, types.StatusClosed)
	if err != nil {
		return nil, fmt.Errorf("failed to close issue: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return nil, fmt.Errorf("failed to get rows affected: %w", err)
	}
	if rows == 0 {
		var status string
		qerr := tx.QueryRowContext(ctx,
			fmt.Sprintf(`SELECT status FROM %s WHERE id = ?`, issueTable), id,
		).Scan(&status)
		if qerr == sql.ErrNoRows {
			return nil, fmt.Errorf("%w: issue %s", storage.ErrNotFound, id)
		}
		if qerr != nil {
			return nil, fmt.Errorf("failed to check issue existence: %w", qerr)
		}
		if types.Status(status) == types.StatusClosed {
			return &CloseResult{IsWisp: isWisp, AlreadyClosed: true}, nil
		}
		return nil, fmt.Errorf("failed to close issue: %s", id)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check which storage driver is in use; ensure it is the supported dolthub/driver interface and not a proxy that drops affected-rows metadata.
  2. Retry the close operation with a fresh transaction and connection.
  3. Update the driver/storage layer if running an old or patched driver version.
  4. If reproducible, capture the wrapped error and report it — this path should never fail with a healthy driver.
Defensive patterns

Strategy: retry

Validate before calling

// ensure a supported driver is configured
if !isSupportedStorageDriver(cfg.Driver) {
    return fmt.Errorf("unsupported storage driver %q: RowsAffected may not be available", cfg.Driver)
}

Try / catch

err := store.CloseIssue(ctx, id, actor, reason)
if err != nil && strings.Contains(err.Error(), "failed to get rows affected") {
    // retry once; if persistent, driver is broken — report it
    return retryClose(ctx, id, actor, reason, 1)
}

Prevention

When it happens

Trigger: tx.ExecContext succeeded but result.RowsAffected() returns an error: unsupported by the driver for the statement type, driver/connection error after executing, or a driver implementation that cannot report rows affected.

Common situations: Using a driver or proxy (e.g. an unusual Dolt/MySQL proxy) that doesn't implement the affected-rows protocol correctly; connection reset right after the UPDATE; custom driver in the storage boundary that doesn't support RowsAffected.

Related errors


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