Encode additional ActionExecution failures with FailureDetails

Deletes an undetailed ActionExecutionException overload.

Deletes TargetOutOfDateException because its distinguished type is
nowhere meaningful.

RELNOTES: None.
PiperOrigin-RevId: 315842104
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionException.java b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionException.java
index 6aa9d7f..87cb251 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionException.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionException.java
@@ -73,15 +73,6 @@
   }
 
   public ActionExecutionException(
-      String message, ActionAnalysisMetadata action, boolean catastrophe) {
-    super(message);
-    this.action = action;
-    this.catastrophe = catastrophe;
-    this.detailedExitCode = DetailedExitCode.justExitCode(ExitCode.BUILD_FAILURE);
-    this.rootCauses = rootCausesFromAction(action, detailedExitCode);
-  }
-
-  public ActionExecutionException(
       String message,
       ActionAnalysisMetadata action,
       boolean catastrophe,
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FailAction.java b/src/main/java/com/google/devtools/build/lib/actions/FailAction.java
index e169ddc..3b6e9b8 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FailAction.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FailAction.java
@@ -17,7 +17,11 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.server.FailureDetails.FailAction.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.Fingerprint;
 
 /**
@@ -49,7 +53,16 @@
   @Override
   public ActionResult execute(ActionExecutionContext actionExecutionContext)
       throws ActionExecutionException {
-    throw new ActionExecutionException(errorMessage, this, false);
+    throw new ActionExecutionException(
+        errorMessage,
+        this,
+        false,
+        DetailedExitCode.of(
+            FailureDetail.newBuilder()
+                .setMessage("FailAction intentional failure")
+                .setFailAction(
+                    FailureDetails.FailAction.newBuilder().setCode(Code.INTENTIONAL_FAILURE))
+                .build()));
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/actions/TargetOutOfDateException.java b/src/main/java/com/google/devtools/build/lib/actions/TargetOutOfDateException.java
deleted file mode 100644
index ee68861..0000000
--- a/src/main/java/com/google/devtools/build/lib/actions/TargetOutOfDateException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.lib.actions;
-
-/**
- * An exception indicating that a target is out of date.
- */
-public class TargetOutOfDateException extends ActionExecutionException {
-
-  public TargetOutOfDateException(Action action) {
-    super (action.prettyPrint() + " is not up-to-date", action, false);
-  }
-
-}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index 95e66af..be63362 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1374,8 +1374,10 @@
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
         "//src/main/java/com/google/devtools/build/lib/util",
+        "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+        "//src/main/protobuf:failure_details_java_proto",
         "//third_party:guava",
         "//third_party:jsr305",
     ],
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java b/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java
index 6684a62..8c43ba2 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java
@@ -24,7 +24,11 @@
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
 import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.server.FailureDetails.Execution;
+import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.protobuf.Extension;
 import com.google.protobuf.MessageLite;
@@ -62,8 +66,15 @@
   @Override
   public ActionResult execute(ActionExecutionContext actionExecutionContext)
       throws ActionExecutionException {
-    throw new ActionExecutionException(
-        mnemonic + "ExtraAction should not be executed.", this, false);
+    String message = mnemonic + "ExtraAction should not be executed.";
+    DetailedExitCode detailedCode =
+        DetailedExitCode.of(
+            FailureDetail.newBuilder()
+                .setMessage(message)
+                .setExecution(
+                    Execution.newBuilder().setCode(Code.PSEUDO_ACTION_EXECUTION_PROHIBITED))
+                .build());
+    throw new ActionExecutionException(message, this, false, detailedCode);
   }
 
   @Override
@@ -86,7 +97,7 @@
     try {
       return super.getExtraActionInfo(actionKeyContext).setExtension(infoExtension, getInfo());
     } catch (CommandLineExpansionException e) {
-      throw new AssertionError("PsedoAction command line expansion cannot fail");
+      throw new AssertionError("PseudoAction command line expansion cannot fail");
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
index 73c58e4..c5c7562 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
@@ -26,8 +26,12 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.server.FailureDetails.SymlinkAction.Code;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -203,32 +207,32 @@
 
     Path inputPath = actionExecutionContext.getInputPath(getPrimaryInput());
     try {
-      // Validate that input path is a file with the executable bit is set.
+      // Validate that input path is a file with the executable bit set.
       if (!inputPath.isFile()) {
+        String message =
+            String.format("'%s' is not a file", getInputs().getSingleton().prettyPrint());
         throw new ActionExecutionException(
-            "'" + getInputs().getSingleton().prettyPrint() + "' is not a file", this, false);
+            message, this, false, createDetailedExitCode(message, Code.EXECUTABLE_INPUT_NOT_FILE));
       }
       if (!inputPath.isExecutable()) {
+        String message =
+            String.format(
+                "failed to create symbolic link '%s': file '%s' is not executable",
+                Iterables.getOnlyElement(getOutputs()).prettyPrint(),
+                getInputs().getSingleton().prettyPrint());
         throw new ActionExecutionException(
-            "failed to create symbolic link '"
-                + Iterables.getOnlyElement(getOutputs()).prettyPrint()
-                + "': file '"
-                + getInputs().getSingleton().prettyPrint()
-                + "' is not executable",
-            this,
-            false);
+            message, this, false, createDetailedExitCode(message, Code.EXECUTABLE_INPUT_IS_NOT));
       }
     } catch (IOException e) {
-      throw new ActionExecutionException(
-          "failed to create symbolic link '"
-              + Iterables.getOnlyElement(getOutputs()).prettyPrint()
-              + "' to the '"
-              + getInputs().getSingleton().prettyPrint()
-              + "' due to I/O error: "
-              + e.getMessage(),
-          e,
-          this,
-          false);
+      String message =
+          String.format(
+              "failed to create symbolic link '%s' to the '%s' due to I/O error: %s",
+              Iterables.getOnlyElement(getOutputs()).prettyPrint(),
+              getInputs().getSingleton().prettyPrint(),
+              e.getMessage());
+      DetailedExitCode detailedExitCode =
+          createDetailedExitCode(message, Code.EXECUTABLE_INPUT_CHECK_IO_EXCEPTION);
+      throw new ActionExecutionException(message, e, this, false, detailedExitCode);
     }
   }
 
@@ -314,4 +318,12 @@
   public boolean mayInsensitivelyPropagateInputs() {
     return true;
   }
+
+  private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
+    return DetailedExitCode.of(
+        FailureDetail.newBuilder()
+            .setMessage(message)
+            .setSymlinkAction(FailureDetails.SymlinkAction.newBuilder().setCode(detailedCode))
+            .build());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
index 69aee2d..5c3d5e5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -105,6 +105,7 @@
         "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/go",
         "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/platform",
         "//src/main/java/com/google/devtools/build/lib/util",
+        "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
         "//src/main/java/com/google/devtools/build/lib/util:filetype",
         "//src/main/java/com/google/devtools/build/lib/util:os",
         "//src/main/java/com/google/devtools/build/lib/util:shell_escaper",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
index 01919c5..7ba1879 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
@@ -81,6 +81,7 @@
 import com.google.devtools.build.lib.syntax.Sequence;
 import com.google.devtools.build.lib.syntax.StarlarkList;
 import com.google.devtools.build.lib.util.DependencySet;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.util.ShellEscaper;
 import com.google.devtools.build.lib.util.io.FileOutErr;
@@ -534,13 +535,12 @@
       try {
         options = getCompilerOptions();
       } catch (CommandLineExpansionException e) {
-        throw new ActionExecutionException(
-            "failed to generate compile command for rule '"
-                + getOwner().getLabel()
-                + ": "
-                + e.getMessage(),
-            this,
-            /* catastrophe= */ false);
+        String message =
+            String.format(
+                "failed to generate compile command for rule '%s: %s",
+                getOwner().getLabel(), e.getMessage());
+        DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+        throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
       }
       commandLineKey = computeCommandLineKey(options);
       List<PathFragment> systemIncludeDirs = getSystemIncludeDirs(options);
@@ -625,8 +625,9 @@
           throw lostInputsExceptionForTimedOutNestedSetExpansion(entry.getKey(), iterator, e);
         }
         BugReport.sendBugReport(e);
-        throw new ActionExecutionException(
-            "Timed out expanding modules", this, /*catastrophe=*/ false);
+        String message = "Timed out expanding modules";
+        DetailedExitCode code = createDetailedExitCode(message, Code.MODULE_EXPANSION_TIMEOUT);
+        throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
       }
 
       for (Artifact module : modules) {
@@ -882,13 +883,12 @@
     try {
       return getEnvironment(ImmutableMap.of());
     } catch (CommandLineExpansionException e) {
-      throw new ActionExecutionException(
-          "failed to generate compile environment variables for rule '"
-              + getOwner().getLabel()
-              + ": "
-              + e.getMessage(),
-          this,
-          /* catastrophe= */ false);
+      String message =
+          String.format(
+              "failed to generate compile environment variables for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
     }
   }
 
@@ -1111,12 +1111,13 @@
         includePath = includePath.relativeTo(prefix);
       }
       if (includePath.isAbsolute() || includePath.containsUplevelReferences()) {
-        throw new ActionExecutionException(
+        String message =
             String.format(
                 "The include path '%s' references a path outside of the execution root.",
-                includePath),
-            this,
-            false);
+                includePath);
+        DetailedExitCode code =
+            createDetailedExitCode(message, Code.INCLUDE_PATH_OUTSIDE_EXEC_ROOT);
+        throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
       }
     }
   }
@@ -1404,13 +1405,12 @@
                 ParameterFileType.GCC_QUOTED,
                 StandardCharsets.ISO_8859_1);
       } catch (CommandLineExpansionException e) {
-        throw new ActionExecutionException(
-            "failed to generate compile command for rule '"
-                + getOwner().getLabel()
-                + ": "
-                + e.getMessage(),
-            this,
-            /* catastrophe= */ false);
+        String message =
+            String.format(
+                "failed to generate compile command for rule '%s: %s",
+                getOwner().getLabel(), e.getMessage());
+        DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+        throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
       }
     }
 
@@ -1508,13 +1508,12 @@
           getOutputs(),
           estimateResourceConsumptionLocal());
     } catch (CommandLineExpansionException e) {
-      throw new ActionExecutionException(
-          "failed to generate compile command for rule '"
-              + getOwner().getLabel()
-              + ": "
-              + e.getMessage(),
-          this,
-          /* catastrophe= */ false);
+      String message =
+          String.format(
+              "failed to generate compile command for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
     }
   }
 
@@ -1659,13 +1658,12 @@
                   .build());
       return discoveredInputs;
     } catch (CommandLineExpansionException e) {
-      throw new ActionExecutionException(
-          "failed to generate compile environment variables for rule '"
-              + getOwner().getLabel()
-              + ": "
-              + e.getMessage(),
-          this,
-          /* catastrophe= */ false);
+      String message =
+          String.format(
+              "failed to generate compile environment variables for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
     }
   }
 
@@ -1931,6 +1929,10 @@
     }
   }
 
+  static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
+    return DetailedExitCode.of(createFailureDetail(message, detailedCode));
+  }
+
   private static FailureDetail createFailureDetail(String message, Code detailedCode) {
     return FailureDetail.newBuilder()
         .setMessage(message)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
index 47918a8..a8c4e603 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
@@ -56,12 +56,16 @@
 import com.google.devtools.build.lib.exec.SpawnStrategyResolver;
 import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode;
 import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.server.FailureDetails.CppLink;
+import com.google.devtools.build.lib.server.FailureDetails.CppLink.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skylarkbuildapi.CommandLineArgsApi;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Location;
 import com.google.devtools.build.lib.syntax.Sequence;
 import com.google.devtools.build.lib.syntax.StarlarkList;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.util.ShellEscaper;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -333,13 +337,12 @@
           getOutputs(),
           estimateResourceConsumptionLocal());
     } catch (CommandLineExpansionException e) {
-      throw new ActionExecutionException(
-          "failed to generate link command for rule '"
-              + getOwner().getLabel()
-              + ": "
-              + e.getMessage(),
-          this,
-          /* catastrophe= */ false);
+      String message =
+          String.format(
+              "failed to generate link command for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
     }
   }
 
@@ -395,9 +398,12 @@
         FileSystemUtils.touchFile(actionExecutionContext.getInputPath(output));
       }
     } catch (IOException | CommandLineExpansionException e) {
-      throw new ActionExecutionException("failed to create fake link command for rule '"
-                                         + getOwner().getLabel() + ": " + e.getMessage(),
-                                         this, false);
+      String message =
+          String.format(
+              "failed to create fake link command for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.FAKE_COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, false, code);
     }
   }
 
@@ -585,4 +591,12 @@
       throw new EvalException(Location.BUILTIN, exception);
     }
   }
+
+  private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
+    return DetailedExitCode.of(
+        FailureDetail.newBuilder()
+            .setMessage(message)
+            .setCppLink(CppLink.newBuilder().setCode(detailedCode))
+            .build());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
index 88ba5a1..3ec4a8b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FakeCppCompileAction.java
@@ -42,6 +42,8 @@
 import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
 import com.google.devtools.build.lib.rules.cpp.CcCommon.CoptsFilter;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.server.FailureDetails.CppCompile.Code;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.ShellEscaper;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
@@ -250,13 +252,12 @@
                   })
               .collect(joining(" "));
     } catch (CommandLineExpansionException e) {
-      throw new ActionExecutionException(
-          "failed to generate compile command for rule '"
-              + getOwner().getLabel()
-              + ": "
-              + e.getMessage(),
-          this,
-          /* catastrophe= */ false);
+      String message =
+          String.format(
+              "failed to generate compile command for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, /*catastrophe=*/ false, code);
     }
 
     // Write the command needed to build the real .o file to the fake .o file.
@@ -285,8 +286,12 @@
               + argv
               + "\n");
     } catch (IOException e) {
-      throw new ActionExecutionException("failed to create fake compile command for rule '"
-          + getOwner().getLabel() + ": " + e.getMessage(), this, false);
+      String message =
+          String.format(
+              "failed to create fake compile command for rule '%s: %s",
+              getOwner().getLabel(), e.getMessage());
+      DetailedExitCode code = createDetailedExitCode(message, Code.FAKE_COMMAND_GENERATION_FAILURE);
+      throw new ActionExecutionException(message, this, false, code);
     }
     return ActionResult.create(spawnResults);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java
index 4994af6..4988e77 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeProblems.java
@@ -17,6 +17,10 @@
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionExecutionException;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.server.FailureDetails.CppCompile;
+import com.google.devtools.build.lib.server.FailureDetails.CppCompile.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 
 /**
  * Accumulator for problems encountered while reading or validating inclusion
@@ -45,7 +49,14 @@
 
   void assertProblemFree(Action action, Artifact sourceFile) throws ActionExecutionException {
     if (hasProblems()) {
-      throw new ActionExecutionException(getMessage(action, sourceFile), action, false);
+      String message = getMessage(action, sourceFile);
+      DetailedExitCode code =
+          DetailedExitCode.of(
+              FailureDetail.newBuilder()
+                  .setMessage(message)
+                  .setCppCompile(CppCompile.newBuilder().setCode(Code.UNDECLARED_INCLUSIONS))
+                  .build());
+      throw new ActionExecutionException(message, action, false, code);
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
index bf50286..62f908f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
@@ -35,6 +35,10 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.server.FailureDetails.LtoAction;
+import com.google.devtools.build.lib.server.FailureDetails.LtoAction.Code;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -135,13 +139,13 @@
         if (!line.isEmpty()) {
           PathFragment execPath = PathFragment.create(line);
           if (execPath.isAbsolute()) {
-            throw new ActionExecutionException(
-                "Absolute paths not allowed in imports file "
-                    + actionExecutionContext.getInputPath(imports)
-                    + ": "
-                    + execPath,
-                this,
-                false);
+            String message =
+                String.format(
+                    "Absolute paths not allowed in imports file %s: %s",
+                    actionExecutionContext.getInputPath(imports), execPath);
+            DetailedExitCode code =
+                createDetailedExitCode(message, Code.INVALID_ABSOLUTE_PATH_IN_IMPORTS);
+            throw new ActionExecutionException(message, this, false, code);
           }
           importSet.add(PathFragment.create(line));
         }
@@ -166,7 +170,7 @@
               bitcodeInputSet.toList().stream()
                   .map(Artifact::getExecPath)
                   .collect(Collectors.toSet()));
-      throw new ActionExecutionException(
+      String message =
           String.format(
               "error computing inputs from imports file: %s, missing bitcode files (first 10): %s",
               actionExecutionContext.getInputPath(imports),
@@ -175,9 +179,9 @@
                   .map(Object::toString)
                   .sorted()
                   .limit(10)
-                  .collect(Collectors.joining(", "))),
-          this,
-          false);
+                  .collect(Collectors.joining(", ")));
+      DetailedExitCode code = createDetailedExitCode(message, Code.MISSING_BITCODE_FILES);
+      throw new ActionExecutionException(message, this, false, code);
     }
     updateInputs(
         NestedSetBuilder.fromNestedSet(bitcodeInputSet)
@@ -186,6 +190,14 @@
     return bitcodeInputSet;
   }
 
+  private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
+    return DetailedExitCode.of(
+        FailureDetail.newBuilder()
+            .setMessage(message)
+            .setLtoAction(LtoAction.newBuilder().setCode(detailedCode))
+            .build());
+  }
+
   @Override
   public NestedSet<Artifact> getMandatoryInputs() {
     return mandatoryInputs;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
index fc60abd..f7002e3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
@@ -1064,8 +1064,10 @@
         // We don't create a specific cause for the artifact as we do in #handleMissingFile because
         // it likely has no label, so we'd have to use the Action's label anyway. Just use the
         // default ActionFailed event constructed by ActionExecutionException.
-        throw new ActionExecutionException(
-            "discovered input file does not exist", actionForError, false);
+        String message = "discovered input file does not exist";
+        DetailedExitCode code =
+            createDetailedExitCode(message, Code.DISCOVERED_INPUT_DOES_NOT_EXIST);
+        throw new ActionExecutionException(message, actionForError, false, code);
       }
       if (retrievedMetadata instanceof TreeArtifactValue) {
         TreeArtifactValue treeValue = (TreeArtifactValue) retrievedMetadata;
@@ -1758,15 +1760,20 @@
   private static ActionExecutionException createMissingInputsException(
       Action action, List<LabelCause> missingArtifactCauses) {
     String message = missingArtifactCauses.size() + " input file(s) do not exist";
+    Code detailedCode = Code.ACTION_INPUT_FILES_MISSING;
     return new ActionExecutionException(
         message,
         action,
         NestedSetBuilder.wrap(Order.STABLE_ORDER, missingArtifactCauses),
         /*catastrophe=*/ false,
-        DetailedExitCode.of(
-            FailureDetail.newBuilder()
-                .setMessage(message)
-                .setExecution(Execution.newBuilder().setCode(Code.ACTION_INPUT_FILES_MISSING))
-                .build()));
+        createDetailedExitCode(message, detailedCode));
+  }
+
+  private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
+    return DetailedExitCode.of(
+        FailureDetail.newBuilder()
+            .setMessage(message)
+            .setExecution(Execution.newBuilder().setCode(detailedCode))
+            .build());
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
index 7a002d4..b477855 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
@@ -68,7 +68,6 @@
 import com.google.devtools.build.lib.actions.ScanningActionEvent;
 import com.google.devtools.build.lib.actions.SpawnResult.MetadataLog;
 import com.google.devtools.build.lib.actions.StoppedScanningActionEvent;
-import com.google.devtools.build.lib.actions.TargetOutOfDateException;
 import com.google.devtools.build.lib.actions.UserExecException;
 import com.google.devtools.build.lib.actions.cache.MetadataHandler;
 import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
@@ -85,10 +84,15 @@
 import com.google.devtools.build.lib.remote.options.RemoteOptions;
 import com.google.devtools.build.lib.rules.cpp.IncludeScannable;
 import com.google.devtools.build.lib.runtime.KeepGoingOption;
+import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.server.FailureDetails.Execution;
+import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.ActionExecutionState.ActionStep;
 import com.google.devtools.build.lib.skyframe.ActionExecutionState.ActionStepOrResult;
 import com.google.devtools.build.lib.skyframe.ActionExecutionState.SharedActionCallback;
 import com.google.devtools.build.lib.util.CrashFailureDetails;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.util.io.FileOutErr;
 import com.google.devtools.build.lib.vfs.FileStatus;
 import com.google.devtools.build.lib.vfs.FileSystem;
@@ -379,8 +383,6 @@
   /**
    * Executes the provided action on the current thread. Returns the ActionExecutionValue with the
    * result, either computed here or already computed on another thread.
-   *
-   * <p>For use from {@link ArtifactFunction} only.
    */
   @SuppressWarnings("SynchronizeOnNonFinalField")
   ActionExecutionValue executeAction(
@@ -397,7 +399,9 @@
       // We can't execute an action (e.g. because --check_???_up_to_date option was used). Fail
       // the build instead.
       synchronized (reporter) {
-        TargetOutOfDateException e = new TargetOutOfDateException(action);
+        String message = action.prettyPrint() + " is not up-to-date";
+        DetailedExitCode code = createDetailedExitCode(message, Code.ACTION_NOT_UP_TO_DATE);
+        ActionExecutionException e = new ActionExecutionException(message, action, false, code);
         reporter.handle(Event.error(e.getMessage()));
         recordExecutionError();
         throw e;
@@ -865,7 +869,11 @@
               logger.atWarning().withCause(e).log(
                   "failed to delete output files before executing action: '%s'", action);
               throw toActionExecutionException(
-                  "failed to delete output files before executing action", e, action, null);
+                  "failed to delete output files before executing action",
+                  e,
+                  action,
+                  null,
+                  Code.ACTION_OUTPUTS_DELETION_FAILURE);
             }
           } else {
             // There's nothing to delete when the action file system is used, but we must ensure
@@ -1032,7 +1040,8 @@
               "not all outputs were created or valid",
               null,
               action,
-              outputAlreadyDumped ? null : fileOutErr);
+              outputAlreadyDumped ? null : fileOutErr,
+              Code.ACTION_OUTPUTS_NOT_CREATED);
         }
 
         if (outputService != null && finalizeActions) {
@@ -1041,7 +1050,12 @@
             outputService.finalizeAction(action, metadataHandler);
           } catch (EnvironmentalExecException | IOException e) {
             logger.atWarning().withCause(e).log("unable to finalize action: '%s'", action);
-            throw toActionExecutionException("unable to finalize action", e, action, fileOutErr);
+            throw toActionExecutionException(
+                "unable to finalize action",
+                e,
+                action,
+                fileOutErr,
+                Code.ACTION_FINALIZATION_FAILURE);
           }
         }
 
@@ -1465,19 +1479,33 @@
    * @param action The action that failed
    * @param actionOutput The output of the failed Action. May be null, if there is no output to
    *     display
+   * @param detailedCode The fine-grained failure code describing the failure
    */
   private ActionExecutionException toActionExecutionException(
-      String message, Throwable cause, Action action, FileOutErr actionOutput) {
+      String message,
+      Throwable cause,
+      Action action,
+      FileOutErr actionOutput,
+      FailureDetails.Execution.Code detailedCode) {
+    DetailedExitCode code = createDetailedExitCode(message, detailedCode);
     ActionExecutionException ex;
     if (cause == null) {
-      ex = new ActionExecutionException(message, action, false);
+      ex = new ActionExecutionException(message, action, false, code);
     } else {
-      ex = new ActionExecutionException(message, cause, action, false);
+      ex = new ActionExecutionException(message, cause, action, false, code);
     }
     printError(ex.getMessage(), action, actionOutput);
     return ex;
   }
 
+  private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
+    return DetailedExitCode.of(
+        FailureDetail.newBuilder()
+            .setMessage(message)
+            .setExecution(Execution.newBuilder().setCode(detailedCode))
+            .build());
+  }
+
   /**
    * For the action 'action' that failed due to 'message' with the output 'actionOutput', notify the
    * user about the error. To notify the user, the method first displays the output of the action
diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto
index 58d1844..a776044 100644
--- a/src/main/protobuf/failure_details.proto
+++ b/src/main/protobuf/failure_details.proto
@@ -135,6 +135,10 @@
     StarlarkAction starlark_action = 162;
     NinjaAction ninja_action = 163;
     DynamicExecution dynamic_execution = 164;
+    FailAction fail_action = 166;
+    SymlinkAction symlink_action = 167;
+    CppLink cpp_link = 168;
+    LtoAction lto_action = 169;
   }
 
   reserved 102; // For internal use
@@ -372,6 +376,12 @@
     SYMLINK_TREE_CREATION_COMMAND_EXCEPTION = 18
         [(metadata) = { exit_code: 36 }];
     ACTION_INPUT_READ_IO_EXCEPTION = 19 [(metadata) = { exit_code: 36 }];
+    ACTION_NOT_UP_TO_DATE = 20 [(metadata) = { exit_code: 1 }];
+    PSEUDO_ACTION_EXECUTION_PROHIBITED = 21 [(metadata) = { exit_code: 1 }];
+    DISCOVERED_INPUT_DOES_NOT_EXIST = 22 [(metadata) = { exit_code: 1 }];
+    ACTION_OUTPUTS_DELETION_FAILURE = 23 [(metadata) = { exit_code: 1 }];
+    ACTION_OUTPUTS_NOT_CREATED = 24 [(metadata) = { exit_code: 1 }];
+    ACTION_FINALIZATION_FAILURE = 25 [(metadata) = { exit_code: 1 }];
   }
 
   Code code = 1;
@@ -805,6 +815,7 @@
     ABNORMAL_TERMINATION = 2 [(metadata) = { exit_code: 1 }];
     EXEC_FAILED = 3 [(metadata) = { exit_code: 1 }];
     PARSE_FAILURE = 4 [(metadata) = { exit_code: 36 }];
+    VALIDATION_FAILURE = 5 [(metadata) = { exit_code: 1 }];
   }
 
   Code code = 1;
@@ -839,6 +850,11 @@
     FIND_USED_HEADERS_IO_EXCEPTION = 1 [(metadata) = { exit_code: 36 }];
     COPY_OUT_ERR_FAILURE = 2 [(metadata) = { exit_code: 36 }];
     D_FILE_READ_FAILURE = 3 [(metadata) = { exit_code: 36 }];
+    COMMAND_GENERATION_FAILURE = 4 [(metadata) = { exit_code: 1 }];
+    MODULE_EXPANSION_TIMEOUT = 5 [(metadata) = { exit_code: 1 }];
+    INCLUDE_PATH_OUTSIDE_EXEC_ROOT = 6 [(metadata) = { exit_code: 1 }];
+    FAKE_COMMAND_GENERATION_FAILURE = 7 [(metadata) = { exit_code: 1 }];
+    UNDECLARED_INCLUSIONS = 8 [(metadata) = { exit_code: 1 }];
   }
 
   Code code = 1;
@@ -871,3 +887,43 @@
 
   Code code = 1;
 }
+
+message FailAction {
+  enum Code {
+    FAIL_ACTION_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+    INTENTIONAL_FAILURE = 1 [(metadata) = { exit_code: 1 }];
+  }
+
+  Code code = 1;
+}
+
+message SymlinkAction {
+  enum Code {
+    SYMLINK_ACTION_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+    EXECUTABLE_INPUT_NOT_FILE = 1 [(metadata) = { exit_code: 1 }];
+    EXECUTABLE_INPUT_IS_NOT = 2 [(metadata) = { exit_code: 1 }];
+    EXECUTABLE_INPUT_CHECK_IO_EXCEPTION = 3 [(metadata) = { exit_code: 1 }];
+  }
+
+  Code code = 1;
+}
+
+message CppLink {
+  enum Code {
+    CPP_LINK_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+    COMMAND_GENERATION_FAILURE = 1 [(metadata) = { exit_code: 1 }];
+    FAKE_COMMAND_GENERATION_FAILURE = 2 [(metadata) = { exit_code: 1 }];
+  }
+
+  Code code = 1;
+}
+
+message LtoAction {
+  enum Code {
+    LTO_ACTION_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+    INVALID_ABSOLUTE_PATH_IN_IMPORTS = 1 [(metadata) = { exit_code: 1 }];
+    MISSING_BITCODE_FILES = 2 [(metadata) = { exit_code: 1 }];
+  }
+
+  Code code = 1;
+}
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
index de31ed7..08d23db 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
@@ -1151,12 +1151,18 @@
   public void testSuccessfulActionsAreNotPublishedByDefault() {
     EventBusHandler handler = new EventBusHandler();
     eventBus.register(handler);
-
     ActionExecutedEvent failedActionExecutedEvent =
         new ActionExecutedEvent(
             ActionsTestUtil.DUMMY_ARTIFACT.getExecPath(),
             new ActionsTestUtil.NullAction(),
-            new ActionExecutionException("Exception", /* action= */ null, /* catastrophe= */ false),
+            new ActionExecutionException(
+                "Exception",
+                /* action= */ null,
+                /* catastrophe= */ false,
+                DetailedExitCode.of(
+                    FailureDetail.newBuilder()
+                        .setSpawn(Spawn.newBuilder().setCode(Code.EXECUTION_DENIED))
+                        .build())),
             ActionsTestUtil.DUMMY_ARTIFACT.getPath(),
             /* stdout= */ null,
             /* stderr= */ null,
@@ -1192,7 +1198,14 @@
         new ActionExecutedEvent(
             ActionsTestUtil.DUMMY_ARTIFACT.getExecPath(),
             new ActionsTestUtil.NullAction(),
-            new ActionExecutionException("Exception", /* action= */ null, /* catastrophe= */ false),
+            new ActionExecutionException(
+                "Exception",
+                /* action= */ null,
+                /* catastrophe= */ false,
+                DetailedExitCode.of(
+                    FailureDetail.newBuilder()
+                        .setSpawn(Spawn.newBuilder().setCode(Code.EXECUTION_DENIED))
+                        .build())),
             ActionsTestUtil.DUMMY_ARTIFACT.getPath(),
             /* stdout= */ null,
             /* stderr= */ null,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ParallelBuilderTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ParallelBuilderTest.java
index d4b3e65..a406dc5 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ParallelBuilderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ParallelBuilderTest.java
@@ -41,10 +41,14 @@
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.events.EventKind;
 import com.google.devtools.build.lib.events.PrintingEventHandler;
+import com.google.devtools.build.lib.server.FailureDetails.Crash;
+import com.google.devtools.build.lib.server.FailureDetails.Crash.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.testutil.BlazeTestUtils;
 import com.google.devtools.build.lib.testutil.Suite;
 import com.google.devtools.build.lib.testutil.TestSpec;
 import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.vfs.FileStatus;
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -684,7 +688,12 @@
                   throw new RuntimeException(e);
                 }
                 completedTasks.getAndIncrement();
-                throw new ActionExecutionException("This is a catastrophe", this, true);
+                DetailedExitCode code =
+                    DetailedExitCode.of(
+                        FailureDetail.newBuilder()
+                            .setCrash(Crash.newBuilder().setCode(Code.CRASH_UNKNOWN))
+                            .build());
+                throw new ActionExecutionException("This is a catastrophe", this, true, code);
               }
               return super.execute(actionExecutionContext);
             }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
index 1ee43a4..dc4a43d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
@@ -53,11 +53,15 @@
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventCollector;
 import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.server.FailureDetails.Crash;
+import com.google.devtools.build.lib.server.FailureDetails.Crash.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.ActionTemplateExpansionValue.ActionTemplateExpansionKey;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationDepsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
 import com.google.devtools.build.lib.testutil.TestUtils;
 import com.google.devtools.build.lib.util.CrashFailureDetails;
+import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.vfs.FileStatus;
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -1052,7 +1056,13 @@
     @Override
     public ActionResult execute(ActionExecutionContext actionExecutionContext)
         throws ActionExecutionException {
-      throw new ActionExecutionException("Throwing dummy action", this, /*catastrophe=*/ true);
+      DetailedExitCode code =
+          DetailedExitCode.of(
+              FailureDetail.newBuilder()
+                  .setCrash(Crash.newBuilder().setCode(Code.CRASH_UNKNOWN))
+                  .build());
+      throw new ActionExecutionException(
+          "Throwing dummy action", this, /*catastrophe=*/ true, code);
     }
   }
 }