gastownhall/beads · error

apply their values for %s %s: %w

Error message

apply their values for %s %s: %w

What it means

The UPDATE that writes the peer's (their) values into the base table failed at the driver/database level. This wraps the raw ExecContext error; the conflict is intentionally left unresolved so the operator can diagnose rather than half-apply a resolution. Causes range from constraint violations to connection loss.

Source

Thrown at internal/storage/versioncontrolops/conflicts.go:409

		}
		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)
			}
			sets[i] = fmt.Sprintf("`%s` = ?", n)
			args = append(args, vals[i])
		}
		args = append(args, ourKey)
		stmt := fmt.Sprintf("UPDATE `%s` SET %s WHERE `%s` = ?", table, strings.Join(sets, ", "), keyCol) //nolint:gosec // identifiers validated above
		res, err := db.ExecContext(ctx, stmt, args...)
		if err != nil {
			return fmt.Errorf("apply their values for %s %s: %w", table, key, err)
		}
		// Zero rows would mean the row we read the conflict for is no longer
		// there — another session on the same branch deleted it between the
		// read and the write. Clearing the conflict now would discard their
		// side under a --theirs invocation, undetectably. But zero is not
		// proof of that on its own (see conflictTargetStillPresent), so ask
		// before refusing: an operator who named this row deserves the abort
		// only when the row really is gone.
		if n, err := res.RowsAffected(); err != nil || n == 0 {
			present, err := conflictTargetStillPresent(ctx, db, table, keyCol, ourKey)
			if err != nil {
				return fmt.Errorf("confirm %s %s still exists after writing their values: %w", table, key, err)
			}
			if !present {
				return fmt.Errorf("their values for %s %s matched no row (was it deleted concurrently?); conflict left unresolved", table, key)
			}
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped cause to see the exact SQL error (constraint, type, lock)
  2. Reconcile the schema/type difference between branches, then resolve again
  3. Run resolution inside a single transaction/session to avoid lock conflicts with concurrent writers
  4. If a constraint blocks it, resolve that row by hand or relax/fix the constraint before applying theirs

Example fix

// before
resolveOne(ctx, db, "issues", "id", "42", "theirs") // UPDATE fails: FK violation
// after
// fix referencing rows or use ON DELETE/UPDATE CASCADE, then:
resolveOne(ctx, db, "issues", "id", "42", "theirs")
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check constraints that their values must satisfy
// e.g. verify no NULLs/unique clashes will be introduced by theirs
row, err := loadConflictRow(ctx, db, table, keyCol, key)
if err != nil { return err }
if err := checkConstraints(ctx, db, table, row, "theirs"); err != nil { return err }

Try / catch

err := resolveOne(ctx, db, table, keyCol, key, "theirs")
if err != nil && strings.Contains(err.Error(), "apply their values for") {
    // conflict left unresolved; inspect cause and fix schema/constraints
    log.Printf("row-level theirs blocked: %v", err)
    return resolveWithOperatorReview(ctx, db, table, keyCol, key)
}
return err

Prevention

When it happens

Trigger: ResolveConflictRows -> resolveOneConflictRow (theirs) executes `UPDATE <table> SET their values WHERE <keyCol> = ?`; the server rejects it (constraint violation, type mismatch after schema merge, lock timeout) or the connection fails.

Common situations: Their values violate a NOT NULL/UNIQUE/FK constraint that only the peer's schema enforced; schema merge changed a column type so their value no longer fits; row locked by another transaction; server restarted mid-resolution.

Related errors


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