{"record":{"id":"60329f6963592850","repo":"gastownhall/beads","slug":"checking-s-sentinel-column-s-s-w","errorCode":null,"errorMessage":"checking %s sentinel column %s.%s: %w","messagePattern":"checking (.+?) sentinel column (.+?)\\.(.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":1280,"sourceCode":"// `RENAME TABLE __temp__x TO x` only when x does not already exist, DROPping\n// the temp otherwise; later migrations gate their ALTERs on\n// INFORMATION_SCHEMA lookups. So re-running the series repairs the missing\n// tables and leaves existing data untouched — which is why this can heal\n// rather than merely diagnose.\nfunc (m migrationSource) cursorContradictedBySchema(ctx context.Context, db DBConn) (bool, error) {\n\tfor _, table := range m.sentinelTables {\n\t\tpresent, err := sentinelTableExists(ctx, db, table)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"checking %s sentinel table %s: %w\", m.cursorTable, table, err)\n\t\t}\n\t\tif !present {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\tfor _, column := range m.sentinelColumns {\n\t\tpresent, err := sentinelColumnExists(ctx, db, column.table, column.column)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"checking %s sentinel column %s.%s: %w\", m.cursorTable, column.table, column.column, err)\n\t\t}\n\t\tif !present {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\n// sentinelTableExists is a function variable for the same reason\n// issueRowCounter is: it lets the cursor-reality tests exercise the real\n// decision without a live database.\nvar sentinelTableExists = func(ctx context.Context, db DBConn, table string) (bool, error) {\n\tvar n int\n\tif err := db.QueryRowContext(ctx,\n\t\t`SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES\n\t\t WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,\n\t\ttable).Scan(&n); err != nil {\n\t\treturn false, err","sourceCodeStart":1262,"sourceCodeEnd":1298,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L1262-L1298","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npresent, err := sentinelColumnExists(ctx, db, column.table, column.column)\nif err != nil {\n    return false, fmt.Errorf(\"checking %s sentinel column %s.%s: %w\", m.cursorTable, column.table, column.column, err)\n}\n// after\npresent, err := sentinelColumnExists(ctx, db, column.table, column.column)\nif err != nil {\n    if dberrors.IsBadConn(err) {\n        present, err = sentinelColumnExists(ctx, db, column.table, column.column) // retry with fresh conn from pool\n    }\n    if err != nil {\n        return false, fmt.Errorf(\"checking %s sentinel column %s.%s: %w\", m.cursorTable, column.table, column.column, err)\n    }\n}","handlingStrategy":"retry","validationCode":"if err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"cannot run sentinel column probe: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := MigrateUp(ctx, db)\nif err != nil && strings.Contains(err.Error(), \"sentinel column\") {\n    time.Sleep(time.Second)\n    err = MigrateUp(ctx, db) // probe is read-only; safe to retry\n}","preventionTips":["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."],"tags":["database","dolt","migration","consistency-check"],"backgroundTag":"sentinel-column-check-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}