gastownhall/beads · error
conflict for %s %s is not a modify/modify conflict (one side
Error message
conflict for %s %s is not a modify/modify conflict (one side has no row); resolve it with a whole-table strategy or edit the row directly
What it means
The conflict is a delete/modify or add/add case: one branch has no row for this key (our_<keyCol> or their_<keyCol> is NULL). The library deliberately refuses row-level ours/theirs here because blindly applying 'theirs' would silently resurrect a deleted row or 'ours' would destroy one, without the operator ever seeing it. It must be resolved with a whole-table strategy or by hand-editing the row.
Source
Thrown at internal/storage/versioncontrolops/conflicts.go:383
}
// resolveOneConflictRow applies strategy to a single modify/modify row.
//
// "ours" is dolt's manual-resolution path: the working set already holds our
// values, so deleting the conflict row *is* the resolution. "theirs" first
// writes their values over ours, then deletes the conflict row — the order
// matters, since the delete is what tells dolt the row is settled.
func resolveOneConflictRow(ctx context.Context, db DBConn, table, keyCol, key, strategy string, row rawConflictRow) error {
ourKey, ourOK := row.value("our", keyCol)
theirKey, theirOK := row.value("their", keyCol)
if !ourOK || !theirOK {
return fmt.Errorf("conflict table dolt_conflicts_%s has no our_%s/their_%s column", table, keyCol, keyCol)
}
if ourKey == nil || theirKey == nil {
// delete/modify (one side removed the row) or add/add against a
// missing key: refuse by name. Row-level ours/theirs would silently
// resurrect or destroy a row the operator never looked at.
return fmt.Errorf("conflict for %s %s is not a modify/modify conflict (one side has no row); "+
"resolve it with a whole-table strategy or edit the row directly", table, key)
}
if strategy == ConflictStrategyTheirs {
names, vals := row.theirFields(keyCol)
if len(names) == 0 {
return fmt.Errorf("conflict for %s %s carries no their_* data columns", table, key)
}
sets := make([]string, len(names))
args := make([]any, 0, len(names)+1)
for i, n := range names {
// Column names are interpolated (MySQL cannot bind an
// identifier) and come from the conflict table's own schema,
// which a peer's schema merge can extend — gate them exactly
// like the table name rather than trusting the source.
if err := ValidateConflictTable(n); err != nil {
return fmt.Errorf("refusing to write unexpected column %q of %s: %w", n, table, err)
}View on GitHub (pinned to 71377f2769)
Solutions
- Re-create the row manually if you want it back, then clear the conflict
- Delete the surviving row if the deletion is correct, then clear the conflict
- Use a whole-table strategy (`--theirs`/`--ours` for the table) if you accept either side wholesale
- Inspect both sides first: SELECT the our_/their_ columns of dolt_conflicts_<table> to see what each branch holds
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// detect delete/modify conflicts up front
row, err := loadConflictRow(ctx, db, table, keyCol, key)
if err != nil { return err }
ourKey, _ := row.value("our", keyCol); theirKey, _ := row.value("their", keyCol)
if ourKey == nil || theirKey == nil {
return handleDeleteModify(ctx, db, table, keyCol, key) // explicit operator choice
} Try / catch
err := resolveOne(ctx, db, table, keyCol, key, strategy)
if err != nil && strings.Contains(err.Error(), "not a modify/modify conflict") {
// require explicit operator decision for delete/modify
return promptDeleteModifyResolution(ctx, db, table, keyCol, key)
}
return err Prevention
- Review delete/modify collisions before merging, not during resolution
- Coordinate row deletions across branches (soft deletes instead of hard deletes)
- Use whole-table strategy when many delete/modify conflicts are expected
- Audit cross-branch row lifecycle in team workflows
When it happens
Trigger: ResolveConflictRows -> resolveOneConflictRow sees ourKey == nil or theirKey == nil for the named key — one side deleted the row while the other modified it, or both added rows that dolt records against a missing base key.
Common situations: Branch A deletes an issue while branch B edits it, then both merge; re-adding a row that was deleted on the other branch; bulk deletes colliding with concurrent edits during a sync.
Related errors
- query conflicts for table %s: %w
- query conflict for %s %s: %w
- no live conflict for %s %s
- multiple conflict rows for %s %s; resolve the whole table in
- conflict table dolt_conflicts_%s has no our_%s/their_%s colu
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/698ed3d60f2dd969.
Report an issue: GitHub.