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

  1. Re-create the row manually if you want it back, then clear the conflict
  2. Delete the surviving row if the deletion is correct, then clear the conflict
  3. Use a whole-table strategy (`--theirs`/`--ours` for the table) if you accept either side wholesale
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/698ed3d60f2dd969. Report an issue: GitHub.