gastownhall/beads · error

probing %s existence: %w

Error message

probing %s existence: %w

What it means

currentVersion first probes cursor-table existence with a guaranteed-to-succeed INFORMATION_SCHEMA COUNT(*) query — deliberately, because a failing statement poisons a pooled Dolt connection's catalog snapshot for the life of the session (be-bv7x). If even this safe probe fails, the error is wrapped as `probing <cursor> existence: <cause>`, indicating a connection/server-level fault rather than schema state.

Source

Thrown at internal/storage/schema/schema.go:1220

	if err != nil {
		return false
	}
	return current >= m.latest()
}

func (m migrationSource) currentVersion(ctx context.Context, db DBConn) (int, error) {
	// Probe existence with a query that always SUCCEEDS before ever issuing one
	// that can fail. A Dolt session that issues a failing statement stays
	// pinned to its pre-statement catalog snapshot, so a bare SELECT against a
	// not-yet-created cursor table poisons the pooled connection: tables
	// created afterwards on other connections stay invisible to this one for
	// 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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry with fresh connections; if errors mention 'bad connection' or 'invalid connection', recycle the pool.
  2. Verify the DSN names an existing schema so DATABASE() resolves correctly.
  3. Add startup retry/backoff so MigrateUp waits for the DB to become reachable.
  4. Confirm the DB user can read INFORMATION_SCHEMA (usually implicit, but restricted setups can deny it).

Example fix

// before
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM information_schema.tables WHERE ...", m.cursorTable).Scan(&cursorExists); err != nil {
    return 0, fmt.Errorf("probing %s existence: %w", m.cursorTable, err)
}
// after
if err := db.PingContext(ctx); err != nil {
    return 0, fmt.Errorf("database unreachable before probing %s: %w", m.cursorTable, err)
}
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM information_schema.tables WHERE ...", m.cursorTable).Scan(&cursorExists); err != nil {
    return 0, fmt.Errorf("probing %s existence: %w", m.cursorTable, err)
}
Defensive patterns

Strategy: retry

Validate before calling

// wait for the DB before starting migrations
for i := 0; i < 30; i++ {
    if err := db.PingContext(ctx); err == nil { break }
    time.Sleep(2 * time.Second)
}

Type guard

func isConnErr(err error) bool {
    var netErr net.Error
    return errors.As(err, &netErr) || strings.Contains(err.Error(), "connection refused")
}

Try / catch

err := MigrateUp(ctx, db)
if err != nil && strings.Contains(err.Error(), "probing ") {
    // existence-probe failure is connectivity-level; wait and retry
    time.Sleep(5 * time.Second)
    err = MigrateUp(ctx, db)
}

Prevention

When it happens

Trigger: The `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?` scan fails: dead pooled connection, context timeout, permission denial on INFORMATION_SCHEMA, or server unavailability.

Common situations: App boots while the database is still starting (container orchestration race); stale pool connections after a server restart; wait_timeout reaping idle connections; wrong DSN database so DATABASE() is null/unexpected.

Related errors


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