gastownhall/beads · error
reading %s version: %w
Error message
reading %s version: %w
What it means
After confirming the cursor table exists, currentVersion runs `SELECT COALESCE(MAX(version), 0) FROM <cursorTable>` to read the applied migration version. If this read fails with something other than ErrNoRows or a table-not-exist error (both handled as 'version 0'), the error is wrapped as `reading <cursor> version: <cause>`.
Source
Thrown at internal/storage/schema/schema.go:1232
// the rest of its life in the pool (be-bv7x).
var cursorExists int
if err := db.QueryRowContext(ctx,
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?",
m.cursorTable,
).Scan(&cursorExists); err != nil {
return 0, fmt.Errorf("probing %s existence: %w", m.cursorTable, err)
}
if cursorExists == 0 {
return 0, nil
}
var current int
err := db.QueryRowContext(ctx, "SELECT COALESCE(MAX(version), 0) FROM "+m.cursorTable).Scan(¤t)
if err != nil && err != sql.ErrNoRows {
if dberrors.IsTableNotExist(err) {
return 0, nil
}
return 0, fmt.Errorf("reading %s version: %w", m.cursorTable, err)
}
if current == 0 {
return 0, nil
}
// A missing cursor TABLE already meant "nothing applied". A cursor whose
// tables are absent means the same thing and was previously believed
// (gh 5033, gh 4356): ignored_schema_migrations is itself dolt-ignored and
// clone-local, so a database materialized out of band — table-by-table
// copy, dump restore, or a clone that picked up the cursor rows without
// the clone-local tables they describe — arrives claiming at-latest with
// no wisps tables. atLatest() then short-circuits migrationWorkNeeded()
// and the series never re-runs, surfacing much later and much further away
// as "table not found: wisp_dependencies" on `bd close`.
contradicted, cerr := m.cursorContradictedBySchema(ctx, db)
if cerr != nil {
return 0, cerr
}
if contradicted {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped cause: for permission errors, GRANT SELECT on the cursor table to the app user.
- If the table looks corrupted or incompatible (e.g. version column missing), drop or repair the cursor table and re-run MigrateUp — migrations are re-runnable.
- Retry on a fresh connection; a Dolt session that previously hit a failing statement can hold a stale snapshot even when the probe succeeded.
- Verify no concurrent DDL is renaming/altering the cursor table during startup.
Example fix
// before
err := db.QueryRowContext(ctx, "SELECT COALESCE(MAX(version), 0) FROM "+m.cursorTable).Scan(¤t)
if err != nil && err != sql.ErrNoRows {
if dberrors.IsTableNotExist(err) {
return 0, nil
}
return 0, fmt.Errorf("reading %s version: %w", m.cursorTable, err)
}
// after
err := db.QueryRowContext(ctx, "SELECT COALESCE(MAX(version), 0) FROM "+m.cursorTable).Scan(¤t)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
if dberrors.IsTableNotExist(err) {
return 0, nil
}
if dberrors.IsBadField(err) { // cursor schema drifted; treat as unapplied
return 0, nil
}
return 0, fmt.Errorf("reading %s version: %w", m.cursorTable, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// check cursor table shape before reading version
cols, err := probeColumns(ctx, db, cursorTable)
if err == nil && !slices.Contains(cols, "version") {
return fmt.Errorf("cursor table %s missing version column; drop it and re-run migrations", cursorTable)
} Type guard
func isCursorSchemaDrift(err error) bool {
return dberrors.IsTableNotExist(err) || dberrors.IsBadField(err) || errors.Is(err, sql.ErrNoRows)
} Try / catch
err := MigrateUp(ctx, db)
if err != nil && strings.Contains(err.Error(), "reading ") && strings.Contains(err.Error(), "version") {
// benign causes are already mapped to version 0; anything here is real
log.Printf("cursor version read failed: %v — verifying cursor table integrity", err)
} Prevention
- Never hand-edit or re-type the cursor table's version column.
- Restore clones/dumps atomically so the cursor and its tables arrive together.
- Keep SELECT grants on the cursor table for the app user.
- Avoid mixing pooled sessions that have hit failing statements with Dolt.
When it happens
Trigger: The MAX(version) query fails on an existing cursor table: permission denial on SELECT, corrupted cursor table, unsupported column type in version, connection loss mid-query, or a poisoned Dolt session snapshot masking the table despite the successful existence probe.
Common situations: Cursor table created by a different/older schema (version column renamed or re-typed); partial clone/dump restore leaving a broken cursor; revoked SELECT grants between probe and read; transient network failure.
Related errors
- recording %s in %s: %w
- failed to migrate credential keys: %w
- failed to update encrypted password for peer %s: %w
- failed to initialize schema: %w
- failed to rebuild pool after migration: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3cf3a95daf904b85.
Report an issue: GitHub.