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 reasonView on GitHub (pinned to 71377f2769)
Solutions
- Retry MigrateUp on fresh connections — the probe is read-only and safe to repeat.
- Recycle the connection pool if errors indicate stale sessions (Dolt catalog-snapshot poisoning after failed statements).
- Check the wrapped cause for permission errors and GRANT SELECT on INFORMATION_SCHEMA lookups if restricted.
- 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
- Serialize migrations with a lock so concurrent processes don't poison pooled Dolt sessions.
- Recycle pooled connections after any failed statement on Dolt.
- Use fresh connections for the migration phase of startup.
- Keep retry/backoff around MigrateUp; all sentinel probes are read-only and safe to repeat.
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
- checking %s sentinel column %s.%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/38f5d8a650c004f1.
Report an issue: GitHub.