{"record":{"id":"4a96c2f16f27a513","repo":"gastownhall/beads","slug":"scan-conflict-for-s-s-w","errorCode":null,"errorMessage":"scan conflict for %s %s: %w","messagePattern":"scan conflict for (.+?) (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/conflicts.go","lineNumber":329,"sourceCode":"\tdefer func() { _ = rows.Close() }()\n\n\tcols, err := rows.Columns()\n\tif err != nil {\n\t\treturn rawConflictRow{}, fmt.Errorf(\"conflict columns for table %s: %w\", table, err)\n\t}\n\tif !rows.Next() {\n\t\tif err := rows.Err(); err != nil {\n\t\t\treturn rawConflictRow{}, fmt.Errorf(\"query conflict for %s %s: %w\", table, key, err)\n\t\t}\n\t\treturn rawConflictRow{}, fmt.Errorf(\"no live conflict for %s %s\", table, key)\n\t}\n\tvals := make([]any, len(cols))\n\tptrs := make([]any, len(cols))\n\tfor i := range vals {\n\t\tptrs[i] = &vals[i]\n\t}\n\tif err := rows.Scan(ptrs...); err != nil {\n\t\treturn rawConflictRow{}, fmt.Errorf(\"scan conflict for %s %s: %w\", table, key, err)\n\t}\n\tif rows.Next() {\n\t\treturn rawConflictRow{}, fmt.Errorf(\"multiple conflict rows for %s %s; resolve the whole table instead\", table, key)\n\t}\n\treturn rawConflictRow{cols: cols, vals: vals}, errors.Join(rows.Err(), rows.Close())\n}\n\n// conflictTargetStillPresent reports whether key still names a row of table.\n//\n// It is the matched-rows check the resolvers need after a write, because\n// RowsAffected is rows CHANGED, not rows MATCHED: the DSN sets parseTime and\n// multiStatements but NOT clientFoundRows (doltutil/dsn.go), so an UPDATE the\n// backend normalizes to the bytes already stored reports zero exactly as a\n// vanished row does. Only asking can tell the two apart.\n//\n// It confirms that the key still resolves to a row — NOT that our values are\n// the stored ones. On the autocommit path (an embedded Pull, where db is not a\n// transaction) a row deleted and re-inserted between the UPDATE and this check","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/conflicts.go#L311-L347","documentation":"The driver failed to Scan the conflict row's columns into []any destinations during loadConflictRow. This wraps the raw driver scan error (e.g. unsupported column type or driver-internal conversion failure). It means the conflict row exists but its contents could not be materialized for row-level resolution.","triggerScenarios":"ResolveConflictRows -> loadConflictRow scans `SELECT *` output of dolt_conflicts_<table>; a column type the MySQL driver cannot scan into interface{} (e.g. certain BLOB/JSON/decimal encodings or a corrupted value) causes rows.Scan to fail.","commonSituations":"Tables with exotic column types (JSON, large BLOBs, bit/newer-decimal types) after a dolt version change; driver version mismatch with the dolt sql-server wire format; corrupted conflict metadata after a crash.","solutions":["Read the wrapped driver error to identify the offending column type","Upgrade/align the go-sql-driver/mysql version with the dolt server version","Resolve the conflict with a whole-table strategy (--theirs/--ours on the table) instead of row level","Reproduce the SELECT manually in a dolt sql shell to inspect the conflicting value"],"exampleFix":"// before\nresolveOne(ctx, db, \"issues\", \"id\", key, \"theirs\") // scan fails on JSON col\n// after\nresolveTable(ctx, db, \"issues\", \"theirs\") // whole-table strategy avoids row scan","handlingStrategy":"fallback","validationCode":"// probe the conflict table's column types before row-level resolution\nrows, _ := db.QueryContext(ctx, \"SELECT * FROM dolt_conflicts_\"+table+\" LIMIT 1\")\ncols, _ := rows.Columns(); _ = rows.Close()\n// if exotic types are present, prefer whole-table strategy","typeGuard":null,"tryCatchPattern":"err := resolveOne(ctx, db, table, keyCol, key, \"theirs\")\nif err != nil && strings.Contains(err.Error(), \"scan conflict for\") {\n    // fall back to whole-table resolution\n    return resolveWholeTable(ctx, db, table, \"theirs\")\n}\nreturn err","preventionTips":["Keep go-sql-driver/mysql and dolt versions aligned across peers","Avoid exotic column types on tables synced between branches","Test merges on tables with JSON/BLOB columns before production syncs","Prefer whole-table strategy for wide or oddly-typed tables"],"tags":["database","dolt","scan-error","driver"],"backgroundTag":"sql-scan-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}