Convert alerting into a reusable workflow.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 985ed44..226e09d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -198,39 +198,14 @@
 
   ##############################################################################
 
-  # Depends on all the other jobs to provide a single anchor that indicates the
-  # final status. Status reporting will become more sophisticated in the future
-  # and we can hopefully avoid the need to explicitly list every single job...
-  summary:
-    # Even if you have an explicit if condition, you still need to override
-    # GitHub's default behavior of not running if any dependencies failed.
+  # Aggregate job status and alerting on failures.
+  ci_summary:
     if: always()
-    runs-on: ubuntu-20.04
     needs:
       - setup
       - runtime
       - runtime_small
       - runtime_tracing
-    steps:
-      - name: "Checking out repository"
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
-      - name: Getting failed jobs
-        id: failed_jobs
-        run: |
-          echo '${{ toJson(needs) }}'
-          FAILED_JOBS="$(echo '${{ toJson(needs) }}' \
-            | jq --raw-output \
-            'map_values(select(.result!="success" and .result!="skipped")) | keys | join(",")' \
-          )"
-          echo "failed-jobs=${FAILED_JOBS}" >> $GITHUB_OUTPUT
-          if [[ "${FAILED_JOBS}" != "" ]]; then
-            echo "The following jobs failed: ${FAILED_JOBS}"
-            exit 1
-          fi
-      - name: Posting to Discord
-        uses: sarisia/actions-status-discord@ce8cc68e4e626000136b3c702d049a154243e490 # v1.14.7
-        if: failure() && github.ref_name == 'main'
-        with:
-          webhook: ${{ secrets.DISCORD_WEBHOOK }}
-          description: "The following jobs failed: ${{ steps.failed_jobs.outputs.failed-jobs }}"
-          url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}"
+    uses: ./.github/workflows/workflow_summary.yml
+    with:
+      jobs-json: ${{ toJson(needs) }}
diff --git a/.github/workflows/workflow_summary.yml b/.github/workflows/workflow_summary.yml
new file mode 100644
index 0000000..de19634
--- /dev/null
+++ b/.github/workflows/workflow_summary.yml
@@ -0,0 +1,62 @@
+# Copyright 2024 The IREE Authors
+#
+# Licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+# Checks the result status of each job provided by 'jobs-json' and sends an
+# alert if at least one job failed.
+#
+# Usage:
+#   ```yml
+#   jobs:
+#      job_1:
+#          ...
+#      job_2:
+#          ...
+#      my_summary:
+#        if: always()
+#        needs:
+#          - job_1
+#          - job_2
+#        uses: ./.github/workflows/workflow_summary.yml
+#        with:
+#          jobs-json: ${{ toJson(needs) }}
+#   ```
+
+name: Workflow Summary
+
+on:
+  workflow_call:
+    inputs:
+        jobs-json:
+          type: string
+          description: The output of `toJson(needs)`
+
+permissions:
+  contents: read
+
+jobs:
+  summary:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Getting failed jobs
+        id: failed_jobs
+        run: |
+          echo '${{ inputs.jobs-json }}'
+          FAILED_JOBS="$(echo '${{ inputs.jobs-json }}' \
+            | jq --raw-output \
+            'map_values(select(.result!="success" and .result!="skipped")) | keys | join(",")' \
+          )"
+          echo "failed-jobs=${FAILED_JOBS}" >> $GITHUB_OUTPUT
+          if [[ "${FAILED_JOBS}" != "" ]]; then
+            echo "The following jobs failed: ${FAILED_JOBS}"
+            exit 1
+          fi
+      - name: Posting to Discord
+        uses: sarisia/actions-status-discord@ce8cc68e4e626000136b3c702d049a154243e490 # v1.14.7
+        if: failure() && github.ref_name == 'main' && github.repository_owner == 'iree-org'
+        with:
+          webhook: ${{ secrets.DISCORD_WEBHOOK }}
+          description: "The following jobs failed: ${{ steps.failed_jobs.outputs.failed-jobs }}"
+          url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}"
diff --git a/build_tools/github_actions/configure_ci.py b/build_tools/github_actions/configure_ci.py
index 55247e9..8144aa8 100755
--- a/build_tools/github_actions/configure_ci.py
+++ b/build_tools/github_actions/configure_ci.py
@@ -39,8 +39,8 @@
 import fnmatch
 import json
 import os
-import re
 import pathlib
+import re
 import string
 import subprocess
 import sys
@@ -111,7 +111,12 @@
 RUNNER_ENV_DEFAULT = "prod"
 RUNNER_ENV_OPTIONS = [RUNNER_ENV_DEFAULT, "testing"]
 
-CONTROL_JOBS = frozenset(["setup", "summary"])
+CONTROL_JOB_REGEXES = frozenset(
+    [
+        re.compile("setup"),
+        re.compile(".*summary"),
+    ]
+)
 
 # Jobs to run only on postsubmit by default.
 # They may also run on presubmit only under certain conditions.
@@ -380,7 +385,8 @@
 
     workflow = yaml.load(workflow_file.read_text(), Loader=yaml.SafeLoader)
     all_jobs = set(workflow["jobs"].keys())
-    all_jobs -= CONTROL_JOBS
+    for regex in CONTROL_JOB_REGEXES:
+        all_jobs = {j for j in all_jobs if not regex.match(j)}
 
     if ALL_KEY in all_jobs:
         raise ValueError(f"Workflow has job with reserved name '{ALL_KEY}'")