gastownhall/beads · error

failed to read row version for %s: %w

Error message

failed to read row version for %s: %w

What it means

CheckVersionInTx wraps any non-no-rows error from reading the row_lock column with this message. The version precondition could not be evaluated, so the mutation is aborted to avoid writing without a verified baseline version.

Source

Thrown at internal/storage/issueops/version.go:42

// and refuses. Together they close the read-then-write window that a bare
// read-then-write would leave open.
//
//nolint:gosec // G201: table name comes from WispTableRouting (hardcoded constants)
func CheckVersionInTx(ctx context.Context, tx DBTX, id string, expected int64) error {
	isWisp := IsActiveWispInTx(ctx, tx, id)
	issueTable, _, _, _ := WispTableRouting(isWisp)

	// row_lock is NOT NULL DEFAULT 0, but scan defensively so a NULL maps to 0
	// rather than erroring (mirrors scan.go's RowVersion handling).
	var current sql.NullInt64
	err := tx.QueryRowContext(ctx,
		fmt.Sprintf("SELECT row_lock FROM %s WHERE id = ?", issueTable), id,
	).Scan(&current)
	if errors.Is(err, sql.ErrNoRows) {
		return fmt.Errorf("%w: issue %s", storage.ErrNotFound, id)
	}
	if err != nil {
		return fmt.Errorf("failed to read row version for %s: %w", id, err)
	}
	if current.Int64 != expected {
		return fmt.Errorf("%w: expected %d, got %d", storage.ErrVersionMismatch, expected, current.Int64)
	}
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped driver error for the root cause
  2. Retry transient failures (connection reset, lock wait) with backoff — the whole-attempt retry re-runs the check
  3. Ensure schema is migrated so row_lock exists on the issues table
  4. Check DB user permissions for SELECT on the issues table
Defensive patterns

Strategy: retry

Validate before calling

var lock int
if err := db.QueryRow("SELECT row_lock FROM issues WHERE id = ?", id).Scan(&lock); err != nil { return err }

Try / catch

err := versionedMutate(ctx, id, expected)
if err != nil && !errors.Is(err, storage.ErrNotFound) && !errors.Is(err, storage.ErrVersionMismatch) {
    return retryWithBackoff(func() error { return versionedMutate(ctx, id, expected) })
}

Prevention

When it happens

Trigger: Any versioned mutation (close/delete/update/reopen) while SELECT row_lock FROM issues WHERE id=? fails: connection drop, lock timeout, permissions, or missing row_lock column from schema drift.

Common situations: DB connectivity flaps inside long transactions; migrations that predate the row_lock column; restricted DB roles; Dolt server under load causing timeouts.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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