{"record":{"id":"b13c860a983e5059","repo":"gastownhall/beads","slug":"checking-schema-migration-work-w","errorCode":null,"errorMessage":"checking schema migration work: %w","messagePattern":"checking schema migration work: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/converged.go","lineNumber":58,"sourceCode":"\t// CLI open in uow.openAndInitSchema — pins its schema-init pool with an\n\t// EMPTY DSN database and only USEs the database after GET_LOCK, so a probe\n\t// that merely ASKED whether the session was already on databaseName read\n\t// NULL from DATABASE() and declined on every single invocation: the fast\n\t// path never fired where it was needed.\n\tonTarget, qualifier, err := selectTargetDatabase(ctx, db, databaseName, selector)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !onTarget {\n\t\treturn false, nil\n\t}\n\n\t// Exactly MigrateUp's own gate, and it runs first: on a fresh or\n\t// mid-upgrade database it reports work needed from the cursor probe alone,\n\t// before any statement that could fail against a missing table.\n\tneeded, err := migrationWorkNeeded(ctx, db)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"checking schema migration work: %w\", err)\n\t}\n\tif needed {\n\t\treturn false, nil\n\t}\n\n\t// MigrateUp re-asserts the canonical dolt_ignore patterns ahead of that\n\t// gate precisely because an out-of-band-materialized database can arrive\n\t// with its cursors at-latest and the patterns missing. Read the same\n\t// question instead of writing it: an under-seeded database is not\n\t// converged and must take the locked path, which heals and commits it.\n\t//\n\t// migrationWorkNeeded has just proved mainSource.atLatest, i.e. the main\n\t// cursor is at or past mainSource.latest(); every version-gated pattern's\n\t// flip migration is part of that embedded set, so the gate can be\n\t// evaluated against latest() with no second cursor read.\n\tseeded, err := doltIgnoreSeeded(ctx, db, qualifier, mainSource.latest())\n\tif err != nil {\n\t\treturn false, err","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/converged.go#L40-L76","documentation":"This error means the 'already converged' fast-path check could not determine whether schema migrations are needed because migrationWorkNeeded itself returned an error (cursor probe, content-hash column probe, or backfill-need probe all failed). The fast path is exactly MigrateUp's own gate and runs first, so this typically indicates a fresh or mid-upgrade database with missing tables, or an unhealthy connection. It aborts the fast path; the locked MigrateUp path should still run.","triggerScenarios":"alreadyConverged (used by MigrateUpWithLock) calls migrationWorkNeeded, whose atLatest probes or needsBackfilledCustomStatusesCustomTypes hit a SQL error — missing schema tables on a fresh DB, information_schema failure, connection loss, or cancelled context.","commonSituations":"Opening a brand-new database before any migration ran; connection to the Dolt server dropped at startup; restricted DB user lacking information_schema access; schema damage from an interrupted earlier migration.","solutions":["Run the normal locked MigrateUp path — on a fresh DB this fast-path failure is bypassed by full migration; ensure MigrateUp executes.","Check Dolt server availability and reconnect.","Verify the DB user can read information_schema and the schema tables.","If it persists on an existing DB, read the wrapped cause to find which sub-probe failed and repair that table/schema."],"exampleFix":"// before: abort everything when the fast-path probe fails\nneeded, err := migrationWorkNeeded(ctx, db)\nif err != nil {\n\treturn false, fmt.Errorf(\"checking schema migration work: %w\", err)\n}\n// after: fall through to the locked migrate path on a missing-table probe failure\nneeded, err := migrationWorkNeeded(ctx, db)\nif err != nil && !isMissingTableErr(err) {\n\treturn false, fmt.Errorf(\"checking schema migration work: %w\", err)\n}\nif err != nil || needed {\n\treturn false, nil // fall through to full migration\n}","handlingStrategy":"fallback","validationCode":"// Determine freshness before trusting fast-path probes\nvar tables int\ndb.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()`).Scan(&tables)\nfresh := tables == 0","typeGuard":null,"tryCatchPattern":"converged, err := checkConverged(ctx, db)\nif err != nil {\n\t// fast path could not evaluate — fall back to the locked full-migrate path\n\tconverged = false\n\terr = migrateUpWithLock(ctx, db)\n}","preventionTips":["Always have a full-migration fallback when short-circuit probes fail","Validate DB connectivity before migration checks","Run under a DB user with information_schema access","Treat 'missing table' errors on fresh databases as a normal state, not a fatal one"],"tags":["database","migration","schema"],"backgroundTag":"migration-gate-probe-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}