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(&current)
	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

  1. Inspect the wrapped cause: for permission errors, GRANT SELECT on the cursor table to the app user.
  2. 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.
  3. Retry on a fresh connection; a Dolt session that previously hit a failing statement can hold a stale snapshot even when the probe succeeded.
  4. 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(&current)
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(&current)
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

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


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