gastownhall/beads · error

failed to commit resolved conflicts: %w

Error message

failed to commit resolved conflicts: %w

What it means

CommitResolvedConflicts runs CALL DOLT_COMMIT('-m', ...) to commit the working set after all merge conflicts were auto-resolved. DOLT_COMMIT refuses a working set with outstanding constraint violations (e.g. FK cascade violations, per bd-578h9.14), so the commit fails and the merge cannot settle even though conflict resolution itself succeeded.

Source

Thrown at internal/storage/versioncontrolops/mergesettle.go:581

		//nolint:gosec // G201: table is one of the hardcoded constants above.
		if _, err := db.ExecContext(ctx, "CALL DOLT_ADD('"+table+"')"); err != nil {
			return false, fmt.Errorf("failed to stage %s: %w", table, err)
		}
	}

	return true, nil
}

// CommitResolvedConflicts creates the dolt commit that concludes a merge whose
// conflicts TryAutoResolveMergeConflicts settled. Callers that saw
// resolved=true MUST call this, and only AFTER TryRepairFKCascadeViolations
// has run: DOLT_COMMIT refuses a working set with outstanding constraint
// violations, so a merge carrying both an auto-resolvable conflict and an FK
// cascade violation could never settle while the resolver committed first
// (bd-578h9.14).
func CommitResolvedConflicts(ctx context.Context, db DBConn) error {
	if _, err := db.ExecContext(ctx, "CALL DOLT_COMMIT('-m', 'auto-resolve merge conflicts: metadata, dependencies, schema_migrations, config, issues (field-level three-way merge), labels/comments/events (union)')"); err != nil {
		return fmt.Errorf("failed to commit resolved conflicts: %w", err)
	}
	return nil
}

// dependencyConflictsAreAuditOnly reports whether every conflicted row in the
// dependencies table is the SAME logical edge on both sides that differs only in
// audit columns (created_at/created_by/metadata/thread_id) — the only class safe to
// auto-resolve with --theirs.
//
// It does NOT trust the primary key as proof of a shared edge. With deterministic
// ids the same edge has the same id on every clone, but an issue rename can leave a
// row's surrogate id stale (depid.New(oldID, target)) while issue_id/target have
// already moved (#4259 finding 2), so two genuinely different edges could collide on
// one id. We therefore verify the natural identity — issue_id and the resolved
// target — matches on both sides, and that the type matches, before declaring the
// conflict audit-only. It returns false if any conflicted row differs in identity or
// type, or was deleted on one side (an add/delete conflict).
func dependencyConflictsAreAuditOnly(ctx context.Context, db DBConn) (bool, error) {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check dolt_status for remaining conflicts — any un-resolved table blocks DOLT_COMMIT; resolve or abort them first.
  2. Inspect constraint/FK violations in the resolved tables; delete or repair dangling rows (e.g. orphaned dependencies) before committing.
  3. If the merge cannot be settled automatically, abort (CALL DOLT_MERGE('--abort')) and re-merge after fixing the source data.
  4. Ensure CommitResolvedConflicts runs only after ALL resolvers finished, including constraint-violation cleanup, not just conflict resolution.

Example fix

// before
func CommitResolvedConflicts(ctx context.Context, db DBConn) error {
	if _, err := db.ExecContext(ctx, "CALL DOLT_COMMIT('-m', 'auto-resolve merge conflicts: ...')"); err != nil {
		return fmt.Errorf("failed to commit resolved conflicts: %w", err)
	}
	return nil
}
// after
func CommitResolvedConflicts(ctx context.Context, db DBConn) error {
	var remaining int
	if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dolt_conflicts").Scan(&remaining); err == nil && remaining > 0 {
		return fmt.Errorf("cannot commit: %d tables still have unresolved conflicts", remaining)
	}
	if _, err := db.ExecContext(ctx, "CALL DOLT_COMMIT('-m', 'auto-resolve merge conflicts: ...')"); err != nil {
		return fmt.Errorf("failed to commit resolved conflicts: %w", err)
	}
	return nil
}
Defensive patterns

Strategy: validation

Validate before calling

var remainingConflicts, fkViolations int
db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dolt_conflicts").Scan(&remainingConflicts)
db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dependencies d LEFT JOIN issues i ON d.depends_on_issue_id = i.id WHERE d.depends_on_issue_id IS NOT NULL AND i.id IS NULL").Scan(&fkViolations)
// only call CommitResolvedConflicts when both are 0

Type guard

func workingSetCommitReady(ctx context.Context, db DBConn) bool {
	var conflicts int
	if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dolt_conflicts").Scan(&conflicts); err != nil || conflicts > 0 {
		return false
	}
	return true
}

Try / catch

if err := CommitResolvedConflicts(ctx, db); err != nil {
	// constraint violation suspected: inspect dangling rows, clean them, retry commit
	var dangling int
	db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dependencies d LEFT JOIN issues i ON d.depends_on_issue_id = i.id WHERE i.id IS NULL AND d.depends_on_issue_id IS NOT NULL").Scan(&dangling)
	if dangling > 0 {
		db.ExecContext(ctx, "DELETE FROM dependencies WHERE depends_on_issue_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM issues WHERE id = depends_on_issue_id)")
		err = CommitResolvedConflicts(ctx, db) // retry once
	}
}

Prevention

When it happens

Trigger: SettleMerge → CommitResolvedConflicts calls DOLT_COMMIT while the resolved working set still violates constraints — most commonly foreign-key cascade violations in tables that were force-resolved with '--theirs', or unresolved conflicts remaining in a table not covered by the auto-resolver.

Common situations: A merge carries both an auto-resolvable conflict and an FK violation in another table (the exact bd-578h9.14 scenario); '--theirs' resolution restored rows whose references were deleted on our side; dangling dependency rows referencing issues deleted by the merge.

Related errors


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