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, errView on GitHub (pinned to 71377f2769)
Solutions
- Retry the check on a fresh connection — it is read-only and safe to repeat.
- Check the wrapped cause: fix connectivity or grants it names.
- Quiesce concurrent DDL (single migrator via lock) so sentinel columns are not mid-rename during probes.
- 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
- Avoid concurrent DDL on sentinel tables while migrations run.
- Use a migration lock (GET_LOCK or similar) to serialize migrators.
- Retry transient failures — the check is idempotent and read-only.
- Monitor connection health; stale pooled sessions are the usual culprit on Dolt.
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
- checking %s sentinel table %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/60329f6963592850.
Report an issue: GitHub.