gastownhall/beads · error

checking %s sentinel column %s.%s: %w

Error message

checking %s sentinel column %s.%s: %w

What it means

The second half of cursorContradictedBySchema checks sentinel COLUMNS exist via sentinelColumnExists (schemaColumnExists). If that probe errors, the failure is wrapped as `checking <cursor> sentinel column <table>.<column>: <cause>`. Like the table check, this detects cursors claiming migrations whose schema evidence is missing, so the series can heal by re-running.

Source

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

// `RENAME TABLE __temp__x TO x` only when x does not already exist, DROPping
// the temp otherwise; later migrations gate their ALTERs on
// INFORMATION_SCHEMA lookups. So re-running the series repairs the missing
// tables and leaves existing data untouched — which is why this can heal
// rather than merely diagnose.
func (m migrationSource) cursorContradictedBySchema(ctx context.Context, db DBConn) (bool, error) {
	for _, table := range m.sentinelTables {
		present, err := sentinelTableExists(ctx, db, table)
		if err != nil {
			return false, fmt.Errorf("checking %s sentinel table %s: %w", m.cursorTable, table, err)
		}
		if !present {
			return true, nil
		}
	}
	for _, column := range m.sentinelColumns {
		present, err := sentinelColumnExists(ctx, db, column.table, column.column)
		if err != nil {
			return false, fmt.Errorf("checking %s sentinel column %s.%s: %w", m.cursorTable, column.table, column.column, err)
		}
		if !present {
			return true, nil
		}
	}
	return false, nil
}

// sentinelTableExists is a function variable for the same reason
// issueRowCounter is: it lets the cursor-reality tests exercise the real
// decision without a live database.
var sentinelTableExists = func(ctx context.Context, db DBConn, table string) (bool, error) {
	var n int
	if err := db.QueryRowContext(ctx,
		`SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
		 WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,
		table).Scan(&n); err != nil {
		return false, err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the check on a fresh connection — it is read-only and safe to repeat.
  2. Check the wrapped cause: fix connectivity or grants it names.
  3. Quiesce concurrent DDL (single migrator via lock) so sentinel columns are not mid-rename during probes.
  4. If stale snapshots persist, recycle Dolt pooled connections after any failed statement in the process.

Example fix

// before
present, err := sentinelColumnExists(ctx, db, column.table, column.column)
if err != nil {
    return false, fmt.Errorf("checking %s sentinel column %s.%s: %w", m.cursorTable, column.table, column.column, err)
}
// after
present, err := sentinelColumnExists(ctx, db, column.table, column.column)
if err != nil {
    if dberrors.IsBadConn(err) {
        present, err = sentinelColumnExists(ctx, db, column.table, column.column) // retry with fresh conn from pool
    }
    if err != nil {
        return false, fmt.Errorf("checking %s sentinel column %s.%s: %w", m.cursorTable, column.table, column.column, err)
    }
}
Defensive patterns

Strategy: retry

Validate before calling

if err := db.PingContext(ctx); err != nil {
    return fmt.Errorf("cannot run sentinel column probe: %w", err)
}

Try / catch

err := MigrateUp(ctx, db)
if err != nil && strings.Contains(err.Error(), "sentinel column") {
    time.Sleep(time.Second)
    err = MigrateUp(ctx, db) // probe is read-only; safe to retry
}

Prevention

When it happens

Trigger: The column-existence probe (parameterized INFORMATION_SCHEMA.COLUMNS/TABLES lookup) fails: connection loss, context timeout, permission denial, or a Dolt session pinned to a stale catalog snapshot that cannot see recently created columns.

Common situations: Another process altered the table concurrently while the probe ran; pooled connections surviving server restarts; restricted DB users; dump-restored databases probed during incomplete restore.

Related errors


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