gastownhall/beads · error

failed to check rows affected for issue counter prefix %q: %

Error message

failed to check rows affected for issue counter prefix %q: %w

What it means

After the counter UPDATE succeeds, nextCounterIDTx calls res.RowsAffected() to learn whether a counter row existed. This error wraps a failure of RowsAffected() itself — the Dolt driver could not report the affected-row count for the executed statement. It is a driver-capability/protocol problem, not a data problem.

Source

Thrown at internal/storage/dolt/issues.go:866

}

// nextCounterIDTx atomically increments and returns the next sequential issue ID
// for the given prefix within an existing transaction. Returns the full ID string
// (e.g., "bd-1"). Used by both generateIssueID and generateIssueIDInTable.
func nextCounterIDTx(ctx context.Context, tx *sql.Tx, prefix string) (string, error) {
	// Increment atomically at the DB level to avoid duplicate IDs under
	// concurrent transactions (GH#2002). "last_id = last_id + 1" is evaluated
	// by the DB engine atomically within Dolt's MVCC.

	// Attempt atomic increment of an existing counter row.
	res, err := tx.ExecContext(ctx, "UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?", prefix)
	if err != nil {
		return "", fmt.Errorf("failed to increment issue counter for prefix %q: %w", prefix, err)
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return "", fmt.Errorf("failed to check rows affected for issue counter prefix %q: %w", prefix, err)
	}

	if rowsAffected == 0 {
		// No counter row yet - seed from existing issues before proceeding to
		// avoid collisions with manually-created sequential IDs (GH#2002).
		if seedErr := seedCounterFromExistingIssuesTx(ctx, tx, prefix); seedErr != nil {
			return "", fmt.Errorf("failed to seed issue counter for prefix %q: %w", prefix, seedErr)
		}
		// Retry the atomic increment after seeding.
		res, err = tx.ExecContext(ctx, "UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?", prefix)
		if err != nil {
			return "", fmt.Errorf("failed to increment issue counter after seeding for prefix %q: %w", prefix, err)
		}
		rowsAffected, err = res.RowsAffected()
		if err != nil {
			return "", fmt.Errorf("failed to check rows affected after seeding for prefix %q: %w", prefix, err)
		}
		if rowsAffected == 0 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error; upgrade the Dolt SQL driver and beads to matching, current versions.
  2. Bypass proxies/intermediaries between beads and the Dolt server to rule out metadata stripping.
  3. Retry the operation; if transient, check server logs for connection/protocol errors at that timestamp.
  4. As a fallback, upgrade to a beads version that treats RowsAffected errors via a read-back path instead of failing.

Example fix

// before
rowsAffected, err := res.RowsAffected()
if err != nil {
    return "", fmt.Errorf("failed to check rows affected for issue counter prefix %q: %w", prefix, err)
}
// after (degrade gracefully by reading back the row)
rowsAffected, err := res.RowsAffected()
if err != nil {
    var cur int
    if scanErr := tx.QueryRowContext(ctx, "SELECT last_id FROM issue_counter WHERE prefix = ?", prefix).Scan(&cur); scanErr != nil {
        return "", fmt.Errorf("failed to check rows affected for issue counter prefix %q: %w", prefix, err)
    }
    rowsAffected = 1
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

func isRowsAffectedErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "rows affected")
}

Try / catch

id, err := store.CreateIssue(ctx, issue)
var wrappedErr error
if err != nil && errors.As(err, &wrappedErr) && strings.Contains(err.Error(), "rows affected for issue counter") {
    // driver capability problem: upgrade driver/beads, then retry
    return fmt.Errorf("driver cannot report rows affected; upgrade driver: %w", err)
}

Prevention

When it happens

Trigger: Calling generateIssueIDInTable in counter mode immediately after a successful UPDATE when the driver's RowsAffected() returns a non-nil error (driver version lacking result-metadata support, connection in a bad state after exec, server protocol mismatch).

Common situations: Using an outdated or third-party SQL driver for Dolt that doesn't implement affected-rows reporting for UPDATEs; running against a proxy/middleware that strips result metadata; driver version mismatch with the Dolt server.

Related errors


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