gastownhall/beads · error
scan dependency conflict: %w
Error message
scan dependency conflict: %w
What it means
While iterating rows of dolt_conflicts_dependencies, rows.Scan into 12 sql.NullString columns (both sides of issue/wisp/external dependency fields plus type) failed. Scan errors mean the result set shape or types do not match the scan targets — typically a column-count mismatch or an unconvertible type. The routine returns false so conflicting rows are left for operator review.
Source
Thrown at internal/storage/versioncontrolops/mergesettle.go:625
FROM dolt_conflicts_dependencies`)
if err != nil {
return false, fmt.Errorf("query dependency conflicts: %w", err)
}
defer rows.Close()
for rows.Next() {
var (
ourID, theirID sql.NullString
ourIssue, theirIssue sql.NullString
ourDepIssue, theirDepIssue sql.NullString
ourDepWisp, theirDepWisp sql.NullString
ourDepExt, theirDepExt sql.NullString
ourType, theirType sql.NullString
)
if err := rows.Scan(&ourID, &theirID, &ourIssue, &theirIssue,
&ourDepIssue, &theirDepIssue, &ourDepWisp, &theirDepWisp,
&ourDepExt, &theirDepExt, &ourType, &theirType); err != nil {
return false, fmt.Errorf("scan dependency conflict: %w", err)
}
// One side deleted the edge (add/delete conflict): leave for the operator.
if !ourID.Valid || !theirID.Valid {
return false, nil
}
// Same edge requires the same source issue. A differing issue_id means the
// shared id is stale on one side (e.g. a rename), not a shared edge.
if ourIssue.Valid != theirIssue.Valid || ourIssue.String != theirIssue.String {
return false, nil
}
// ...and the same resolved target.
ourTarget, ourOK := resolveConflictDepTarget(ourDepIssue, ourDepWisp, ourDepExt)
theirTarget, theirOK := resolveConflictDepTarget(theirDepIssue, theirDepWisp, theirDepExt)
if ourOK != theirOK || ourTarget != theirTarget {
return false, nil
}
// A differing type is the only remaining way this is a real semantic conflict.
if ourType.Valid != theirType.Valid || ourType.String != theirType.String {View on GitHub (pinned to 71377f2769)
Solutions
- Run DESCRIBE dolt_conflicts_dependencies and match the SELECT column list and order exactly to the 12 Scan targets.
- Upgrade or align the Dolt/driver version so the conflict-table schema matches what the query expects.
- Scan into sql.RawBytes or interface{} where types are uncertain, then convert explicitly instead of relying on NullString conversion.
- Check the wrapped inner error: 'sql: expected N destination arguments' means a count mismatch; 'converting driver.Value type' means a type mismatch.
Example fix
// before
if err := rows.Scan(&ourID, &theirID, &ourIssue, &theirIssue,
&ourDepIssue, &theirDepIssue, &ourDepWisp, &theirDepWisp,
&ourDepExt, &theirDepExt, &ourType, &theirType); err != nil {
return false, fmt.Errorf("scan dependency conflict: %w", err)
}
// after
cells := make([]any, 12)
ptrs := make([]any, 12)
for i := range cells {
ptrs[i] = &cells[i]
}
if err := rows.Scan(ptrs...); err != nil {
return false, fmt.Errorf("scan dependency conflict: %w", err)
}
toNull := func(v any) sql.NullString {
s, _ := v.(string)
if v == nil { return sql.NullString{} }
return sql.NullString{String: fmt.Sprintf("%v", v), Valid: true}
}
ourID, theirID := toNull(cells[0]), toNull(cells[1]) Defensive patterns
Strategy: type-guard
Validate before calling
cols, err := rows.Columns()
if err != nil || len(cols) != 12 {
// schema drift: do not scan; fall back to operator review
} Type guard
func scanDependencyConflictRow(rows *sql.Rows) (ok bool) {
cols, err := rows.Columns()
if err != nil || len(cols) != 12 {
return false
}
cells := make([]any, len(cols))
ptrs := make([]any, len(cols))
for i := range cells {
ptrs[i] = &cells[i]
}
return rows.Scan(ptrs...) == nil
} Try / catch
auditOnly, err := dependencyConflictsAreAuditOnly(ctx, db)
if err != nil && strings.Contains(err.Error(), "scan dependency conflict") {
// schema mismatch: log and treat conflicts as operator-resolvable
auditOnly = false
} Prevention
- Validate rows.Columns() length before scanning; it is the cheapest schema-drift check.
- Scan into interface{} / sql.RawBytes when column types may vary across Dolt versions.
- Add a startup schema assertion (DESCRIBE dolt_conflicts_dependencies) so drift is caught before merges run.
- On any scan failure, default to leaving conflicts for the operator — never auto-resolve partially parsed rows.
When it happens
Trigger: dependencyConflictsAreAuditOnly reads a row whose column count differs from the 12 expected values (Dolt schema drift), or a column type (e.g. integer/boolean instead of string) cannot scan into sql.NullString.
Common situations: Dolt version upgrade changed dolt_conflicts_dependencies columns (added/removed/reordered); a column became non-nullable-typed (e.g. BIGINT) and the driver refuses to convert into NullString; a driver that returns different types for conflict tables than for normal tables.
Related errors
- scan status: %w
- scan commit: %w
- failed to scan dolt_status: %w
- scan dirty config key: %w
- scan remaining blocker: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6e646dda53f8c554.
Report an issue: GitHub.