{"record":{"id":"41a7d5479bd2d942","repo":"gastownhall/beads","slug":"scan-schema-migrations-conflict-w","errorCode":null,"errorMessage":"scan schema_migrations conflict: %w","messagePattern":"scan schema_migrations conflict: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/mergesettle.go","lineNumber":732,"sourceCode":"// whose content hashes are compatible: equal, or NULL/empty on exactly one side\n// (a pre-#4270 binary recorded the version without a hash, bd-6dnrw.29). Two\n// different non-empty hashes mean the clones applied different content for the\n// same version — the #4259 schema fork — and are never auto-resolved. A row\n// deleted on one side is not a vintage artifact either.\nfunc schemaMigrationsConflictsAreVintageOnly(ctx context.Context, db DBConn) (bool, error) {\n\trows, err := db.QueryContext(ctx, `\n\t\tSELECT our_version, their_version, our_content_hash, their_content_hash\n\t\tFROM dolt_conflicts_schema_migrations`)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"query schema_migrations conflicts: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar ourVersion, theirVersion sql.NullInt64\n\t\tvar ourHash, theirHash sql.NullString\n\t\tif err := rows.Scan(&ourVersion, &theirVersion, &ourHash, &theirHash); err != nil {\n\t\t\treturn false, fmt.Errorf(\"scan schema_migrations conflict: %w\", err)\n\t\t}\n\t\tif !ourVersion.Valid || !theirVersion.Valid || ourVersion.Int64 != theirVersion.Int64 {\n\t\t\treturn false, nil\n\t\t}\n\t\tours, theirs := ourHash.String, theirHash.String\n\t\tif ours != \"\" && theirs != \"\" && ours != theirs {\n\t\t\treturn false, nil // real content skew (#4259) — operator decides\n\t\t}\n\t}\n\treturn true, rows.Err()\n}\n\n// resolveSchemaMigrationsVintageConflicts resolves vintage-only cursor-row\n// conflicts (validated by schemaMigrationsConflictsAreVintageOnly) keeping\n// whichever side recorded a content hash: when theirs has the hash and ours is\n// NULL, the working-set row is updated to theirs before the table-level\n// resolve, so '--ours' never discards recorded provenance.\nfunc resolveSchemaMigrationsVintageConflicts(ctx context.Context, db DBConn) error {","sourceCodeStart":714,"sourceCodeEnd":750,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/mergesettle.go#L714-L750","documentation":"Returned by schemaMigrationsConflictsAreVintageOnly when rows.Scan cannot read a dolt_conflicts_schema_migrations row into (NullInt64, NullInt64, NullString, NullString) for our_version, their_version, our_content_hash, their_content_hash. This indicates the conflict row's shape or types don't match the expected four-column vintage-check layout, so the function aborts rather than risk auto-resolving a real content fork (the #4259 schema fork) as if it were a vintage artifact.","triggerScenarios":"TryAutoResolveMergeConflicts iterates dolt_conflicts_schema_migrations and Scan fails — column count or type mismatch from a Dolt version whose conflict table differs (e.g. pre-#4270 repos without content_hash on both sides), a driver that can't convert a version column into sql.NullInt64, or scanning after the rows iterator already errored.","commonSituations":"Repos created by older bd binaries where schema_migrations lacked content_hash; mixed-version clusters where the merge was written by a different Dolt engine; test doubles returning wrong column counts; corrupt or partially-written conflict rows after a crashed merge.","solutions":["Inspect the wrapped driver error: a column-count/type mismatch means the repo's conflict-table layout differs — run the appropriate bd migration/doctor step or align Dolt versions.","Retry after reconnecting; mid-iteration driver errors poison subsequent Scan calls.","Resolve schema_migrations conflicts manually with dolt conflicts resolve after manually comparing versions and content hashes.","If a crashed merge left partial conflict rows, abort/reset the merge (dolt merge --abort) and re-merge.","For test harnesses, ensure mock rows return exactly four columns matching NullInt64/NullString targets."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Confirm the conflict table exposes the four expected columns before scanning:\nrows, err := db.QueryContext(ctx,\n    \"SELECT column_name FROM information_schema.columns WHERE table_name = 'dolt_conflicts_schema_migrations'\")\n// require our_version, their_version, our_content_hash, their_content_hash;\n// if absent, resolve schema_migrations conflicts manually instead of auto-resolving.","typeGuard":null,"tryCatchPattern":"if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"scan schema_migrations conflict\") {\n        // row shape mismatch — abort the merge and resolve manually:\n        // dolt merge --abort; dolt conflicts resolve ...\n    }\n}","preventionTips":["Keep bd and the Dolt engine on compatible versions to avoid conflict-table layout drift.","After a crashed merge, abort and re-merge rather than scanning partial conflict rows.","Always check rows.Err() after iteration loops to catch poisoning early.","Validate mock drivers in tests return four columns matching NullInt64/NullString targets.","Manually compare migration content hashes before accepting any auto-resolution."],"tags":["dolt","merge-conflicts","schema-migrations","sql-scan"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}