BEP reports analysis failure for targets with artifact conflicts.

Artifact conflicts are detected through a non-skyframe evaluation that is not
capable of identifying a top-level target or aspect that failed as a result of the
artifact conflict. We do perform a skyframe evaluation to identify which targets
and aspects to actually evaluate however, and *this* evaluation is now used to
report artifact conflicts in BEP.

A previous implementation of this change was rolled back due to a crash that
occurred if a --keep_going build included both action conflicts and other analysis
failures. A new regression test is included for this case.

RELNOTES: None.
PiperOrigin-RevId: 368017860
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactPrefixConflictException.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactPrefixConflictException.java
index d6fb317..a908db5 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactPrefixConflictException.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactPrefixConflictException.java
@@ -27,6 +27,8 @@
  * lead to an error if both actions were executed in the same build.
  */
 public class ArtifactPrefixConflictException extends Exception implements DetailedException {
+  private final Label firstOwner;
+
   public ArtifactPrefixConflictException(
       PathFragment firstPath, PathFragment secondPath, Label firstOwner, Label secondOwner) {
     super(
@@ -35,6 +37,11 @@
                 + "These actions cannot be simultaneously present; please rename one of the output "
                 + "files or build just one of them",
             firstPath, firstOwner, secondPath, secondOwner));
+    this.firstOwner = firstOwner;
+  }
+
+  public Label getFirstOwner() {
+    return firstOwner;
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java
index 02c37bf..1e8a0ef 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java
@@ -16,8 +16,10 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionLookupKey;
 import com.google.devtools.build.lib.buildeventstream.BuildEvent;
 import com.google.devtools.build.lib.buildeventstream.BuildEventContext;
 import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil;
@@ -28,6 +30,7 @@
 import com.google.devtools.build.lib.causes.Cause;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.skyframe.AspectValueKey;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
 import java.util.Collection;
 import javax.annotation.Nullable;
@@ -37,13 +40,34 @@
  * target cannot be completed because of an error in one of its dependencies.
  */
 public class AnalysisFailureEvent implements BuildEvent {
+  @Nullable private final AspectValueKey failedAspect;
   private final ConfiguredTargetKey failedTarget;
   private final BuildEventId configuration;
   private final NestedSet<Cause> rootCauses;
 
   public AnalysisFailureEvent(
-      ConfiguredTargetKey failedTarget, BuildEventId configuration, NestedSet<Cause> rootCauses) {
-    this.failedTarget = failedTarget;
+      ActionLookupKey failedTarget, BuildEventId configuration, NestedSet<Cause> rootCauses) {
+    Preconditions.checkArgument(
+        failedTarget instanceof ConfiguredTargetKey || failedTarget instanceof AspectValueKey);
+    if (failedTarget instanceof ConfiguredTargetKey) {
+      this.failedAspect = null;
+      this.failedTarget = (ConfiguredTargetKey) failedTarget;
+    } else {
+      this.failedAspect = (AspectValueKey) failedTarget;
+      this.failedTarget = failedAspect.getBaseConfiguredTargetKey();
+    }
+    if (configuration != null) {
+      this.configuration = configuration;
+    } else {
+      this.configuration = NullConfiguration.INSTANCE.getEventId();
+    }
+    this.rootCauses = rootCauses;
+  }
+
+  public AnalysisFailureEvent(
+      AspectValueKey failedAspect, BuildEventId configuration, NestedSet<Cause> rootCauses) {
+    this.failedAspect = failedAspect;
+    this.failedTarget = failedAspect.getBaseConfiguredTargetKey();
     if (configuration != null) {
       this.configuration = configuration;
     } else {
@@ -55,6 +79,7 @@
   @Override
   public String toString() {
     return MoreObjects.toStringHelper(this)
+        .add("failedAspect", failedAspect)
         .add("failedTarget", failedTarget)
         .add("configuration", configuration)
         .add("legacyFailureReason", getLegacyFailureReason())
@@ -86,7 +111,12 @@
 
   @Override
   public BuildEventId getEventId() {
-    return BuildEventIdUtil.targetCompleted(failedTarget.getLabel(), configuration);
+    if (failedAspect == null) {
+      return BuildEventIdUtil.targetCompleted(failedTarget.getLabel(), configuration);
+    } else {
+      return BuildEventIdUtil.aspectCompleted(
+          failedTarget.getLabel(), configuration, failedAspect.getAspectName());
+    }
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionLookupConflictFindingFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionLookupConflictFindingFunction.java
index c5a542f..eede394 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionLookupConflictFindingFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionLookupConflictFindingFunction.java
@@ -80,7 +80,7 @@
     return Label.print(((ConfiguredTargetKey) skyKey.argument()).getLabel());
   }
 
-  private static class ActionConflictFunctionException extends SkyFunctionException {
+  static class ActionConflictFunctionException extends SkyFunctionException {
     ActionConflictFunctionException(ConflictException e) {
       super(e, Transience.PERSISTENT);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java
index 15fb842..8eb9a8f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java
@@ -199,14 +199,15 @@
       this.apce = e;
     }
 
-    void rethrowTyped() throws ActionConflictException, ArtifactPrefixConflictException {
+    IllegalStateException rethrowTyped()
+        throws ActionConflictException, ArtifactPrefixConflictException {
       if (ace == null) {
         throw Preconditions.checkNotNull(apce);
       }
       if (apce == null) {
         throw Preconditions.checkNotNull(ace);
       }
-      throw new IllegalStateException();
+      throw new IllegalStateException("malformed ConflictException has no well-typed cause");
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
index d123290..63f284d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
@@ -35,8 +35,22 @@
   private static final Interner<StarlarkAspectLoadingKey> starlarkAspectKeyInterner =
       BlazeInterners.newWeakInterner();
 
+  /**
+   * Gets the name of the aspect that would be returned by the corresponding value's {@code
+   * aspectValue.getAspect().getAspectClass().getName()}, if the value could be produced.
+   *
+   * <p>Only needed for reporting errors in BEP when the key's AspectValue fails evaluation.
+   */
+  public abstract String getAspectName();
+
   public abstract String getDescription();
 
+  @Nullable
+  abstract BuildConfigurationValue.Key getAspectConfigurationKey();
+
+  /** Returns the key for the base configured target for this aspect. */
+  public abstract ConfiguredTargetKey getBaseConfiguredTargetKey();
+
   public static AspectKey createAspectKey(
       Label label,
       @Nullable BuildConfiguration baseConfiguration,
@@ -121,6 +135,10 @@
       return SkyFunctions.ASPECT;
     }
 
+    @Override
+    public String getAspectName() {
+      return aspectDescriptor.getDescription();
+    }
 
     @Override
     public Label getLabel() {
@@ -174,11 +192,13 @@
      * base target's configuration.
      */
     @Nullable
+    @Override
     BuildConfigurationValue.Key getAspectConfigurationKey() {
       return aspectConfigurationKey;
     }
 
     /** Returns the key for the base configured target for this aspect. */
+    @Override
     public ConfiguredTargetKey getBaseConfiguredTargetKey() {
       return baseConfiguredTargetKey;
     }
@@ -312,6 +332,11 @@
     }
 
     @Override
+    public String getAspectName() {
+      return String.format("%s%%%s", starlarkFileLabel, starlarkValueName);
+    }
+
+    @Override
     public Label getLabel() {
       return targetLabel;
     }
@@ -322,6 +347,18 @@
       return String.format("%s%%%s of %s", starlarkFileLabel, starlarkValueName, targetLabel);
     }
 
+    @Nullable
+    @Override
+    BuildConfigurationValue.Key getAspectConfigurationKey() {
+      return aspectConfigurationKey;
+    }
+
+    /** Returns the key for the base configured target for this aspect. */
+    @Override
+    public ConfiguredTargetKey getBaseConfiguredTargetKey() {
+      return baseConfiguredTargetKey;
+    }
+
     @Override
     public int hashCode() {
       // We use the hash code caching strategy employed by java.lang.String. There are three subtle
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
index a44dbb8..1a90a8a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
@@ -85,6 +85,7 @@
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.ArtifactConflictFinder.ConflictException;
 import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey;
+import com.google.devtools.build.lib.skyframe.SkyframeExecutor.TopLevelActionConflictReport;
 import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.OrderedSetMultimap;
 import com.google.devtools.build.lib.util.Pair;
@@ -104,9 +105,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
@@ -315,7 +316,7 @@
       eventHandler.handle(
           Event.info(
               "--discard_analysis_cache was used in the previous build, "
-              + "discarding analysis cache."));
+                  + "discarding analysis cache."));
       skyframeExecutor.handleAnalysisInvalidatingChange();
     } else {
       String diff = describeConfigurationDifference(configurations, maxDifferencesToShow);
@@ -479,12 +480,12 @@
             keepGoing,
             eventBus);
     Collection<Exception> reportedExceptions = Sets.newHashSet();
+    ViewCreationFailedException noKeepGoingException = null;
     for (Map.Entry<ActionAnalysisMetadata, ConflictException> bad : actionConflicts.entrySet()) {
       ConflictException ex = bad.getValue();
       DetailedExitCode detailedExitCode;
       try {
-        ex.rethrowTyped();
-        throw new IllegalStateException("ConflictException.rethrowTyped must throw");
+        throw ex.rethrowTyped();
       } catch (ActionConflictException ace) {
         detailedExitCode = ace.getDetailedExitCode();
         ace.reportTo(eventHandler);
@@ -503,24 +504,32 @@
       }
       // TODO(ulfjack): Don't throw here in the nokeep_going case, but report all known issues.
       if (!keepGoing) {
-        throw new ViewCreationFailedException(detailedExitCode.getFailureDetail(), ex);
+        noKeepGoingException =
+            new ViewCreationFailedException(detailedExitCode.getFailureDetail(), ex);
+        if (errors.second != null) {
+          throw noKeepGoingException;
+        }
       }
     }
 
     // This is here for backwards compatibility. The keep_going and nokeep_going code paths were
     // checking action conflicts and analysis errors in different orders, so we only throw the
     // analysis error here after first throwing action conflicts.
-    if (!keepGoing) {
+    //
+    // If there is no other analysis error, we will have not thrown for action conflicts because we
+    // have not yet reported a root cause for the action conflict. Finding that root cause requires
+    // a skyframe evaluation.
+    if (!keepGoing && errors.second != null) {
       throw errors.second;
     }
 
     if (foundActionConflict) {
       // In order to determine the set of configured targets transitively error free from action
       // conflict issues, we run a post-processing update() that uses the bad action map.
-      Predicate<ActionLookupKey> errorFreePredicate;
+      TopLevelActionConflictReport topLevelActionConflictReport;
       enableAnalysis(true);
       try {
-        errorFreePredicate =
+        topLevelActionConflictReport =
             skyframeExecutor.filterActionConflictsForConfiguredTargetsAndAspects(
                 eventHandler,
                 Iterables.concat(ctKeys, aspectKeys),
@@ -529,10 +538,48 @@
       } finally {
         enableAnalysis(false);
       }
+      // Report an AnalysisFailureEvent to BEP for the top-level targets with discoverable action
+      // conflicts, then finally throw if evaluation is --nokeep_going.
+      for (ActionLookupKey ctKey : Iterables.concat(ctKeys, aspectKeys)) {
+        if (!topLevelActionConflictReport.isErrorFree(ctKey)) {
+          Optional<ConflictException> e = topLevelActionConflictReport.getConflictException(ctKey);
+          if (!e.isPresent()) {
+            continue;
+          }
+          AnalysisFailedCause failedCause =
+              makeArtifactConflictAnalysisFailedCause(configurationLookupSupplier, e.get());
+          BuildConfigurationValue.Key configKey =
+              ctKey instanceof ConfiguredTargetKey
+                  ? ((ConfiguredTargetKey) ctKey).getConfigurationKey()
+                  : ((AspectValueKey) ctKey).getAspectConfigurationKey();
+          eventBus.post(
+              new AnalysisFailureEvent(
+                  ctKey,
+                  configurationLookupSupplier.get().get(configKey).toBuildEvent().getEventId(),
+                  NestedSetBuilder.create(Order.STABLE_ORDER, failedCause)));
+          if (!keepGoing) {
+            noKeepGoingException =
+                new ViewCreationFailedException(
+                    failedCause.getDetailedExitCode().getFailureDetail(), e.get());
+          }
+        }
+      }
 
+      // If we're here and we're --nokeep_going, then there was a conflict due to actions not
+      // discoverable by TopLevelActionLookupConflictFindingFunction. This includes extra actions,
+      // coverage artifacts, and artifacts produced by aspects in output groups not present in
+      // --output_groups. Throw the exception produced by the ArtifactConflictFinder which cannot
+      // identify root-cause top-level keys but does catch all possible conflicts.
+      if (!keepGoing) {
+        skyframeExecutor.resetActionConflictsStoredInSkyframe();
+        throw noKeepGoingException;
+      }
+
+      // Filter cts and aspects to only error-free keys. Note that any analysis failure - not just
+      // action conflicts - will be observed here and lead to a key's exclusion.
       cts =
           ctKeys.stream()
-              .filter(errorFreePredicate)
+              .filter(topLevelActionConflictReport::isErrorFree)
               .map(
                   k ->
                       Preconditions.checkNotNull((ConfiguredTargetValue) result.get(k), k)
@@ -541,7 +588,7 @@
 
       aspects =
           aspectKeys.stream()
-              .filter(errorFreePredicate)
+              .filter(topLevelActionConflictReport::isErrorFree)
               .map(result::get)
               .map(AspectValue.class::cast)
               .collect(
@@ -559,6 +606,36 @@
         packageRoots);
   }
 
+  private static AnalysisFailedCause makeArtifactConflictAnalysisFailedCause(
+      Supplier<Map<BuildConfigurationValue.Key, BuildConfiguration>> configurationLookupSupplier,
+      ConflictException e) {
+    try {
+      throw e.rethrowTyped();
+    } catch (ActionConflictException ace) {
+      return makeArtifactConflictAnalysisFailedCause(configurationLookupSupplier, ace);
+    } catch (ArtifactPrefixConflictException apce) {
+      return new AnalysisFailedCause(apce.getFirstOwner(), null, apce.getDetailedExitCode());
+    }
+  }
+
+  private static AnalysisFailedCause makeArtifactConflictAnalysisFailedCause(
+      Supplier<Map<BuildConfigurationValue.Key, BuildConfiguration>> configurationLookupSupplier,
+      ActionConflictException ace) {
+    DetailedExitCode detailedExitCode = ace.getDetailedExitCode();
+    Label causeLabel = ace.getArtifact().getArtifactOwner().getLabel();
+    BuildConfigurationValue.Key causeConfigKey = null;
+    if (ace.getArtifact().getArtifactOwner() instanceof ConfiguredTargetKey) {
+      causeConfigKey =
+          ((ConfiguredTargetKey) ace.getArtifact().getArtifactOwner()).getConfigurationKey();
+    }
+    BuildConfiguration causeConfig =
+        causeConfigKey == null ? null : configurationLookupSupplier.get().get(causeConfigKey);
+    return new AnalysisFailedCause(
+        causeLabel,
+        causeConfig == null ? null : causeConfig.toBuildEvent().getEventId().getConfiguration(),
+        detailedExitCode);
+  }
+
   private boolean shouldCheckForConflicts(ImmutableSet<SkyKey> newKeys) {
     if (someActionLookupValueEvaluated) {
       // A top-level target was added and may introduce a conflict, or a top-level target was
@@ -653,7 +730,8 @@
       ErrorInfo errorInfo = errorEntry.getValue();
       assertValidAnalysisException(errorInfo, errorKey, result.getWalkableGraph());
       skyframeExecutor
-          .getCyclesReporter().reportCycles(errorInfo.getCycleInfo(), errorKey, eventHandler);
+          .getCyclesReporter()
+          .reportCycles(errorInfo.getCycleInfo(), errorKey, eventHandler);
       Exception cause = errorInfo.getException();
       Preconditions.checkState(cause != null || !errorInfo.getCycleInfo().isEmpty(), errorInfo);
 
@@ -726,7 +804,6 @@
                 : NestedSetBuilder.emptySet(Order.STABLE_ORDER);
       } else if (cause instanceof ActionConflictException) {
         ((ActionConflictException) cause).reportTo(eventHandler);
-        // TODO(ulfjack): Report the action conflict.
         rootCauses = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
       } else if (cause instanceof NoSuchPackageException) {
         // This branch is only taken in --nokeep_going builds. In a --keep_going build, the
@@ -953,11 +1030,11 @@
   }
 
   /**
-   * Returns the host configuration trimmed to the same fragments as the input configuration. If
-   * the input is null, returns the top-level host configuration.
+   * Returns the host configuration trimmed to the same fragments as the input configuration. If the
+   * input is null, returns the top-level host configuration.
    *
-   * <p>This may only be called after {@link #setTopLevelHostConfiguration} has set the
-   * correct host configuration at the top-level.
+   * <p>This may only be called after {@link #setTopLevelHostConfiguration} has set the correct host
+   * configuration at the top-level.
    */
   public BuildConfiguration getHostConfiguration(BuildConfiguration config) {
     if (config == null) {
@@ -1013,9 +1090,8 @@
   }
 
   /**
-   * Workaround to clear all legacy data, like the artifact factory. We need
-   * to clear them to avoid conflicts.
-   * TODO(bazel-team): Remove this workaround. [skyframe-execution]
+   * Workaround to clear all legacy data, like the artifact factory. We need to clear them to avoid
+   * conflicts. TODO(bazel-team): Remove this workaround. [skyframe-execution]
    */
   void clearLegacyData() {
     artifactFactory.clear();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 5d99e1d..cd6ffe0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -153,6 +153,7 @@
 import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code;
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.server.FailureDetails.TargetPatterns;
+import com.google.devtools.build.lib.skyframe.ArtifactConflictFinder.ConflictException;
 import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey;
 import com.google.devtools.build.lib.skyframe.DirtinessCheckerUtils.FileDirtinessChecker;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
@@ -2429,14 +2430,11 @@
 
   /**
    * Checks the given action lookup values for action conflicts. Values satisfying the returned
-   * predicate are known to be transitively error-free from action conflicts. {@link
-   * #filterActionConflictsForTopLevelArtifacts} must be called after this to free memory coming
-   * from this call.
-   *
-   * <p>This method is only called in keep-going mode, since otherwise any known action conflicts
-   * will immediately fail the build.
+   * predicate are known to be transitively error-free from action conflicts or other analysis
+   * failures. {@link #resetActionConflictsStoredInSkyframe} must be called after this to free
+   * memory coming from this call.
    */
-  Predicate<ActionLookupKey> filterActionConflictsForConfiguredTargetsAndAspects(
+  TopLevelActionConflictReport filterActionConflictsForConfiguredTargetsAndAspects(
       ExtendedEventHandler eventHandler,
       Iterable<ActionLookupKey> keys,
       ImmutableMap<ActionAnalysisMetadata, ArtifactConflictFinder.ConflictException>
@@ -2458,11 +2456,57 @@
     // of action conflict is rare.
     memoizingEvaluator.delete(
         SkyFunctionName.functionIs(SkyFunctions.TOP_LEVEL_ACTION_LOOKUP_CONFLICT_FINDING));
+    return new TopLevelActionConflictReport(result, topLevelArtifactContext);
+  }
 
-    return k ->
-        result.get(
-                TopLevelActionLookupConflictFindingFunction.Key.create(k, topLevelArtifactContext))
-            != null;
+  /**
+   * Encapsulation of the result of #filterActionConflictsForConfiguredTargetsAndAspects() allowing
+   * callers to determine which top-level keys did not have analysis errors and retrieve the
+   * ConflictException for those that keys that specifically have conflicts.
+   */
+  static final class TopLevelActionConflictReport {
+
+    private final EvaluationResult<ActionLookupConflictFindingValue> result;
+    private final TopLevelArtifactContext topLevelArtifactContext;
+
+    TopLevelActionConflictReport(
+        EvaluationResult<ActionLookupConflictFindingValue> result,
+        TopLevelArtifactContext topLevelArtifactContext) {
+      this.result = result;
+      this.topLevelArtifactContext = topLevelArtifactContext;
+    }
+
+    boolean isErrorFree(ActionLookupKey k) {
+      return result.get(
+              TopLevelActionLookupConflictFindingFunction.Key.create(k, topLevelArtifactContext))
+          != null;
+    }
+
+    /**
+     * Get the ConflictException produced for the given ActionLookupKey. Will throw if the given key
+     * {@link #isErrorFree is error-free}.
+     */
+    Optional<ConflictException> getConflictException(ActionLookupKey k) {
+      ErrorInfo errorInfo =
+          result.getError(
+              TopLevelActionLookupConflictFindingFunction.Key.create(k, topLevelArtifactContext));
+      Exception e = errorInfo.getException();
+      return Optional.ofNullable(e instanceof ConflictException ? (ConflictException) e : null);
+    }
+  }
+
+  /**
+   * Clears all action conflicts stored in skyframe that were discovered by a call to {@link
+   * #filterActionConflictsForConfiguredTargetsAndAspects}.
+   *
+   * <p>This function must be called after a call to {@link
+   * #filterActionConflictsForConfiguredTargetsAndAspects}, either directly (in the case of
+   * no-keep_going evaluations) or indirectly by {@link #filterActionConflictsForTopLevelArtifacts}
+   * in keep_going evaluations.
+   */
+  public void resetActionConflictsStoredInSkyframe() {
+    memoizingEvaluator.delete(
+        SkyFunctionName.functionIs(SkyFunctions.ACTION_LOOKUP_CONFLICT_FINDING));
   }
 
   /**
@@ -2487,8 +2531,7 @@
             eventHandler);
 
     // Remove remaining action-conflict detection values immediately for memory efficiency.
-    memoizingEvaluator.delete(
-        SkyFunctionName.functionIs(SkyFunctions.ACTION_LOOKUP_CONFLICT_FINDING));
+    resetActionConflictsStoredInSkyframe();
 
     return a -> result.get(ActionLookupConflictFindingValue.key(a)) != null;
   }