gastownhall/beads · error

checking %s sentinel table %s: %w

Error message

checking %s sentinel table %s: %w

What it means

cursorContradictedBySchema verifies the cursor's claimed migration version against reality by checking sentinel tables exist. If sentinelTableExists errors for a sentinel table, the failure is wrapped as `checking <cursor> sentinel table <table>: <cause>`. This check exists because a cursor can claim at-latest while its described tables are missing (clone/dump-restore divergence, gh 5033/4356).

Source

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

	return current, nil
}

// cursorContradictedBySchema reports whether this series' cursor claims work
// that the schema does not corroborate.
//
// Returning "cursor is 0" rather than an error is deliberate: the series is
// written to be re-runnable against a database that already has some of it.
// migrations/ignored/0001 builds each table as __temp__<name> and then
// `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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry MigrateUp on fresh connections — the probe is read-only and safe to repeat.
  2. Recycle the connection pool if errors indicate stale sessions (Dolt catalog-snapshot poisoning after failed statements).
  3. Check the wrapped cause for permission errors and GRANT SELECT on INFORMATION_SCHEMA lookups if restricted.
  4. Serialize migrations with a lock so concurrent processes do not poison shared pooled sessions.

Example fix

// before
present, err := sentinelTableExists(ctx, db, table)
if err != nil {
    return false, fmt.Errorf("checking %s sentinel table %s: %w", m.cursorTable, table, err)
}
// after
present, err := sentinelTableExists(ctx, db, table)
if err != nil {
    if dberrors.IsBadConn(err) { // stale pooled session; retry once fresh
        present, err = sentinelTableExists(ctx, freshConn(ctx, db), table)
    }
    if err != nil {
        return false, fmt.Errorf("checking %s sentinel table %s: %w", m.cursorTable, table, err)
    }
}
Defensive patterns

Strategy: retry

Validate before calling

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

Try / catch

err := MigrateUp(ctx, db)
if err != nil && strings.Contains(err.Error(), "sentinel table") {
    // read-only probe failed; recycle pool and retry once
    db.SetMaxIdleConns(0)
    err = MigrateUp(ctx, db)
}

Prevention

When it happens

Trigger: The `SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?` for a sentinel table fails: dead connection, context cancellation, permission issue, or poisoned Dolt session snapshot after an earlier failed statement in the same pooled connection.

Common situations: Long-lived pooled connections surviving a Dolt server restart; concurrent migrations issuing failing DDL on shared connections; network instability during startup; restricted INFORMATION_SCHEMA visibility.

Related errors


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