gastownhall/beads · error

apply merged values for issue %v: %w

Error message

apply merged values for issue %v: %w

What it means

This error wraps the driver/database error returned when an UPDATE that writes field-merged values over an issue's working-set row fails inside the dolt manual conflict-resolution path. resolveIssuesFieldMerge writes merged cells directly to the `issues` table (DOLT_CONFLICTS_RESOLVE cannot express per-cell merges), so any SQL failure — connection loss, schema mismatch, type error, lock timeout — is surfaced here tagged with the issue id. The conflict is left unresolved; the caller (TryAutoResolveMergeConflicts) aborts the auto-merge pass.

Source

Thrown at internal/storage/versioncontrolops/automerge.go:602

		}
		if len(m.columns) > 0 {
			sets := make([]string, len(m.columns))
			args := make([]any, 0, len(m.columns)+1)
			for i, col := range m.columns {
				// MySQL cannot bind an identifier and a peer's schema merge can
				// extend the conflict table's columns, so gate every name the
				// same way the table name is gated.
				if err := ValidateConflictTable(col); err != nil {
					return fmt.Errorf("refusing to write unexpected column %q of issues: %w", col, err)
				}
				sets[i] = fmt.Sprintf("`%s` = ?", col)
				args = append(args, m.values[i])
			}
			args = append(args, m.ourKey)
			stmt := fmt.Sprintf("UPDATE `issues` SET %s WHERE `%s` = ?", strings.Join(sets, ", "), issuesKeyColumn) //nolint:gosec // identifiers validated above
			res, err := db.ExecContext(ctx, stmt, args...)
			if err != nil {
				return fmt.Errorf("apply merged values for issue %v: %w", m.ourKey, err)
			}
			// Zero rows would mean the row we planned against is gone —
			// another session deleted it between the read and the write, and
			// clearing the conflict now would discard their side undetectably.
			// But RowsAffected is rows CHANGED, not rows MATCHED: the DSN does
			// not set clientFoundRows (doltutil/dsn.go), so a write the backend
			// normalizes to the bytes already stored also reports zero. Only a
			// follow-up existence check can tell "vanished" from "no-op".
			if n, err := res.RowsAffected(); err != nil || n == 0 {
				present, err := conflictTargetStillPresent(ctx, db, "issues", issuesKeyColumn, m.ourKey)
				if err != nil {
					return fmt.Errorf("confirm issue %v still exists after writing merged values: %w", m.ourKey, err)
				}
				if !present {
					return fmt.Errorf("merged values for issue %v matched no row (was it deleted concurrently?); conflict left unresolved", m.ourKey)
				}
			}
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run `bd dolt pull`/merge after confirming the database is reachable and writable; the conflict row is untouched so the merge can be retried.
  2. Check the wrapped driver error (%w) for type-mismatch on a specific column; if a peer schema change added it, re-sync schemas and retry.
  3. Verify no other process holds a lock on the Dolt database (e.g. a stale server or second `bd` doing schema work) and retry.
  4. If the error repeats, resolve the conflict manually (DOLT_CONFLICTS_RESOLVE --ours/--theirs or editing the row) and re-run.
  5. Confirm the user the connection uses has UPDATE privilege on `issues` and the working set is not read-only.

Example fix

// before: plan built once, then applied without any schema/type check on values
args = append(args, m.values[i])
res, err := db.ExecContext(ctx, stmt, args...)
// after: coerce merged values through the same normalization used for comparison before binding
for i, col := range m.columns {
    m.values[i] = conflictCellsNormalize(m.values[i])
}
res, err := db.ExecContext(ctx, stmt, args...)
Defensive patterns

Strategy: try-catch

Validate before calling

// before triggering auto-merge, check connectivity and writability
var live int
if err := db.QueryRowContext(ctx, "SELECT 1").Err; err != nil {
    return fmt.Errorf("database unreachable before merge: %w", err)
}
if _, err := db.ExecContext(ctx, "SELECT COUNT(*) FROM `issues` LIMIT 1"); err != nil {
    return fmt.Errorf("issues table not readable/schema mismatch: %w", err)
}

Type guard

func isDriverError(err error) bool {
    var de interface{ Error() string; Unwrap() error }
    return errors.As(err, &de) && strings.Contains(err.Error(), "apply merged values")
}

Try / catch

if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {
    var wrapped interface{ Unwrap() error }
    if errors.As(err, &wrapped) {
        log.Printf("auto-merge failed for issue (driver: %v); conflict left unresolved — retry or resolve manually", errors.Unwrap(err))
    }
    // safe fallback: conflict row remains; a later pull can retry
    return fallbackManualResolve(ctx, db)
}

Prevention

When it happens

Trigger: db.ExecContext on `UPDATE issues SET ... WHERE <key> = ?` fails during auto conflict resolution — e.g. the Dolt/MySQL connection dropped mid-merge, a column added on the peer branch has an incompatible type so a merged value cannot bind, the database is read-only/locked by another process, or the working-set schema diverged from the conflict-table schema the plan was built from.

Common situations: Two `bd` sessions merging concurrently while one connection is killed or times out; a peer schema migration added a column and the merged value's Go type (e.g. []byte vs int64) cannot be bound; running against a replica or read-only database; Dolt server restarted during a long merge.

Related errors


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