{"record":{"id":"ec84ac40ac11e544","repo":"gastownhall/beads","slug":"scan-config-conflict-w","errorCode":null,"errorMessage":"scan config conflict: %w","messagePattern":"scan config conflict: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/mergesettle.go","lineNumber":673,"sourceCode":"// metadata makes). Any other config key in conflict — issue_prefix above all,\n// whose stale-value sweep GH#2455 specifically guards against — is a real\n// semantic conflict, so the whole config table is left for the operator.\n//\n// The key column is config's primary key, so a same-key conflict carries the\n// identical key on both sides; an add/delete conflict leaves one side NULL. A\n// row is convergent only if every key it presents is a memory key.\nfunc configConflictsAreMemoryConvergent(ctx context.Context, db DBConn) (bool, error) {\n\trows, err := db.QueryContext(ctx, `\n\t\tSELECT our_key, their_key FROM dolt_conflicts_config`)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"query config conflicts: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar ourKey, theirKey sql.NullString\n\t\tif err := rows.Scan(&ourKey, &theirKey); err != nil {\n\t\t\treturn false, fmt.Errorf(\"scan config conflict: %w\", err)\n\t\t}\n\t\tfor _, k := range []sql.NullString{ourKey, theirKey} {\n\t\t\tif k.Valid && !strings.HasPrefix(k.String, memoryConfigKeyPrefix) {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn true, rows.Err()\n}\n\n// resolvedConfigConflictKeys returns the keys of the config rows currently in\n// conflict, used only to name the kv.memory.* keys whose local value the\n// --theirs auto-resolution is about to supersede. It must be called BEFORE\n// DOLT_CONFLICTS_RESOLVE clears dolt_conflicts_config. config's primary key is\n// `key`, so a same-key conflict carries the identical key on both sides; an\n// add/delete conflict leaves one side NULL, so COALESCE picks whichever side has\n// it.\nfunc resolvedConfigConflictKeys(ctx context.Context, db DBConn) ([]string, error) {","sourceCodeStart":655,"sourceCodeEnd":691,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/mergesettle.go#L655-L691","documentation":"Returned by configConflictsAreMemoryConvergent when rows.Scan fails while reading a dolt_conflicts_config conflict row into two sql.NullString values (our_key, their_key). The wrapper indicates the row shape returned by the Dolt engine did not match what the code expects — the scan is typed and will fail on NULL-incompatible targets, driver type conversion problems, or iteration misuse. The whole auto-resolution pre-check aborts so no conflict is auto-resolved on uncertain data.","triggerScenarios":"TryAutoResolveMergeConflicts invokes configConflictsAreMemoryConvergent and the Dolt driver returns a row whose our_key/their_key columns cannot be scanned into sql.NullString — e.g. the driver returned a different column count (schema drift across Dolt versions), a column of an unconvertible type, or the caller kept scanning after rows was already in an error state.","commonSituations":"Dolt engine upgrade/downgrade changing the dolt_conflicts_config column layout; a custom or mocked DBConn driver returning rows with mismatched column counts; using a driver whose Rows.Scan doesn't support sql.NullString for the conflict-table column types; reading rows after a network interruption mid-iteration.","solutions":["Check the underlying wrapped error: if it's a column-count mismatch, the Dolt version's conflict-table schema differs — align the bd build with the installed Dolt engine version.","Retry the operation after reconnecting; transient driver errors mid-iteration can poison the rows iterator.","If using a custom driver/mocked DBConn in tests, make its rows return exactly two columns compatible with sql.NullString.","As a workaround, skip auto-resolution and resolve config conflicts manually (dolt conflicts resolve on the config table).","Run 'bd doctor' or equivalent to validate the embedded Dolt storage layer for schema corruption."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate the conflict-table shape before scanning:\ncols, err := db.QueryContext(ctx, \"SHOW COLUMNS FROM dolt_conflicts_config\")\nif err != nil {\n    return err\n}\n// expect exactly our_key and their_key (plus base columns) of string type;\n// if the layout differs, skip auto-resolution and resolve manually.","typeGuard":null,"tryCatchPattern":"if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {\n    var scanErr *sql.ScanError\n    if errors.As(err, &scanErr) {\n        // column/type mismatch: fall back to manual resolution\n    }\n}","preventionTips":["Pin the Dolt engine version your bd build was compiled against.","In tests, make mock DBConn rows return the exact column count/types the code scans into.","Avoid scanning rows after a prior iteration error; always check rows.Err().","After Dolt upgrades, run a smoke merge on a scratch clone before production repos.","Resolve conflicts manually whenever the conflict-table schema looks unfamiliar."],"tags":["dolt","merge-conflicts","sql-scan","storage"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}