{"record":{"id":"442803cf87ae085b","repo":"gastownhall/beads","slug":"failed-to-read-row-version-for-s-w","errorCode":null,"errorMessage":"failed to read row version for %s: %w","messagePattern":"failed to read row version for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/version.go","lineNumber":42,"sourceCode":"// and refuses. Together they close the read-then-write window that a bare\n// read-then-write would leave open.\n//\n//nolint:gosec // G201: table name comes from WispTableRouting (hardcoded constants)\nfunc CheckVersionInTx(ctx context.Context, tx DBTX, id string, expected int64) error {\n\tisWisp := IsActiveWispInTx(ctx, tx, id)\n\tissueTable, _, _, _ := WispTableRouting(isWisp)\n\n\t// row_lock is NOT NULL DEFAULT 0, but scan defensively so a NULL maps to 0\n\t// rather than erroring (mirrors scan.go's RowVersion handling).\n\tvar current sql.NullInt64\n\terr := tx.QueryRowContext(ctx,\n\t\tfmt.Sprintf(\"SELECT row_lock FROM %s WHERE id = ?\", issueTable), id,\n\t).Scan(&current)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, id)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read row version for %s: %w\", id, err)\n\t}\n\tif current.Int64 != expected {\n\t\treturn fmt.Errorf(\"%w: expected %d, got %d\", storage.ErrVersionMismatch, expected, current.Int64)\n\t}\n\treturn nil\n}\n","sourceCodeStart":24,"sourceCodeEnd":49,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/version.go#L24-L49","documentation":"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.","triggerScenarios":"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.","commonSituations":"DB connectivity flaps inside long transactions; migrations that predate the row_lock column; restricted DB roles; Dolt server under load causing timeouts.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"var lock int\nif err := db.QueryRow(\"SELECT row_lock FROM issues WHERE id = ?\", id).Scan(&lock); err != nil { return err }","typeGuard":null,"tryCatchPattern":"err := versionedMutate(ctx, id, expected)\nif err != nil && !errors.Is(err, storage.ErrNotFound) && !errors.Is(err, storage.ErrVersionMismatch) {\n    return retryWithBackoff(func() error { return versionedMutate(ctx, id, expected) })\n}","preventionTips":["Apply backoff retry for transient DB errors inside transactions","Keep row_lock column present via migrations","Ensure SELECT grants for the app DB role"],"tags":["go","database","query-failure","optimistic-lock"],"backgroundTag":"database-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}