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(¤t)
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
- Inspect the wrapped driver error for the root cause
- Retry transient failures (connection reset, lock wait) with backoff — the whole-attempt retry re-runs the check
- Ensure schema is migrated so row_lock exists on the issues table
- 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
- Apply backoff retry for transient DB errors inside transactions
- Keep row_lock column present via migrations
- Ensure SELECT grants for the app DB role
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
- db: CommentSQLRepository.InsertRecord: check issue existence
- db: GetMetadata %s: %w
- failed to read assignee/status for %s: %w
- ErrTransaction
- ErrQuery
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/442803cf87ae085b.
Report an issue: GitHub.