Index: src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java	(revision 14846)
+++ src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java	(working copy)
@@ -59,6 +59,7 @@
 import org.openstreetmap.josm.gui.layer.ValidatorLayer;
 import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
 import org.openstreetmap.josm.io.OsmTransferException;
 import org.openstreetmap.josm.spi.preferences.Config;
@@ -626,6 +627,7 @@
                 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
                 int i = 0;
                 SwingUtilities.invokeAndWait(ds::beginUpdate);
+                tree.setResetScheduled();
                 try {
                     for (TestError error: testErrors) {
                         i++;
@@ -645,7 +647,6 @@
                                 fixCommands.size() > 1 ? new AutofixCommand(fixCommands) : fixCommands.get(0), false);
                     }
                     invalidateValidatorLayers();
-                    tree.resetErrors();
                 });
             } catch (InterruptedException e) {
                 tryUndo();
@@ -657,6 +658,7 @@
                 if (monitor.isCanceled()) {
                     tryUndo();
                 }
+                GuiHelper.runInEDTAndWait(tree::resetErrors);
                 monitor.finishTask();
             }
         }
Index: src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java	(revision 14846)
+++ src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java	(working copy)
@@ -80,8 +80,11 @@
      */
     private transient Set<? extends OsmPrimitive> filter;
 
-    private final ListenerList<Runnable> invalidationListeners = ListenerList.create();
+    private final transient ListenerList<Runnable> invalidationListeners = ListenerList.create();
 
+    /** if true, buildTree() does nothing */
+    private boolean resetScheduled;
+
     /**
      * Constructor
      * @param errors The list of errors
@@ -147,6 +150,8 @@
      * Builds the errors tree
      */
     public void buildTree() {
+        if (resetScheduled)
+            return;
         final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
 
         if (errors == null || errors.isEmpty()) {
@@ -401,6 +406,8 @@
      * Updates the current errors list
      */
     public void resetErrors() {
+        resetScheduled = false;
+        errors.removeIf(error -> error.getPrimitives().stream().anyMatch(OsmPrimitive::isDeleted));
         setErrors(new ArrayList<>(errors));
     }
 
@@ -482,7 +489,9 @@
         // Remove purged primitives (fix #8639)
         if (errors != null) {
             final Set<? extends OsmPrimitive> deletedPrimitives = new HashSet<>(event.getPrimitives());
-            errors.removeIf(error -> error.getPrimitives().stream().anyMatch(deletedPrimitives::contains));
+            if (errors.removeIf(error -> error.getPrimitives().stream().anyMatch(deletedPrimitives::contains))) {
+                buildTree();
+            }
         }
     }
 
@@ -511,6 +520,16 @@
     }
 
     @Override public void dataChanged(DataChangedEvent event) {
-        // Do nothing
+        if (errors.removeIf(error -> error.getPrimitives().stream().anyMatch(OsmPrimitive::isDeleted))) {
+            buildTree();
+        }
     }
+
+    /**
+     * Can be called to suppress execution of buildTree() while doing multiple updates. Caller must
+     * call resetErrors() to end this state.
+     */
+    public void setResetScheduled() {
+        resetScheduled = true;
+    }
 }
