This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-6959-589f601d48025ada64836bbeb3adfc183282bb5c
in repository https://gitbox.apache.org/repos/asf/texera.git

commit cc1c332987a53d528c83f5fbed556a7918b43945
Author: Yicong Huang <[email protected]>
AuthorDate: Mon Jul 27 18:53:08 2026 -0700

    ci: move backport preflight out of Required Checks and report conflicts as 
neutral (#6959)
    
    ### What changes were proposed in this PR?
    
    Follow-up to #6941, improving the backport CI/PRs so they stop reading
    as failures:
    
    - **A backport conflict no longer shows a red X.** Moved backport
    preflight/build out of `required-checks.yml` into a dedicated **Backport
    Checks** workflow, and report a conflict as a **`neutral`** (grey) check
    instead of a failing job. Still advisory, still never blocks merge
    (backport was never a required context).
    - **Shared precheck.** Extracted `precheck` into a reusable workflow so
    the label→stack mapping lives in one place.
    - **Nicer auto-opened draft backport PRs:** opened as
    `github-actions[bot]`, follow the PR template, title no longer links
    back to the original PR (trailing `(#N)` stripped), and the body carries
    the original PR's linked issue/discussion.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6954. Follow-up to #6941.
    
    ### How was this PR tested?
    
    `actionlint` and `node --check` clean on all workflows; the apply-check
    shell/jq and the title/related-section logic were exercised locally.
    Full behavior runs on a real backport-labeled PR.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8)
---
 .github/workflows/backport-auto-label.yml          |  17 +-
 .github/workflows/backport-checks.yml              | 140 +++++++++
 .github/workflows/backport-publish.yml             | 129 +++++++++
 .github/workflows/direct-backport-push.yml         | 214 +++++++++++---
 .../{required-checks.yml => precheck.yml}          | 230 +++------------
 .github/workflows/required-checks.yml              | 315 +--------------------
 6 files changed, 491 insertions(+), 554 deletions(-)

diff --git a/.github/workflows/backport-auto-label.yml 
b/.github/workflows/backport-auto-label.yml
index f10959b8c4..2e4c22d077 100644
--- a/.github/workflows/backport-auto-label.yml
+++ b/.github/workflows/backport-auto-label.yml
@@ -62,11 +62,14 @@ jobs:
         uses: actions/github-script@v9
         env:
           ENTRIES: ${{ steps.targets.outputs.entries }}
+          BOT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
-          # Add the label with the fine-grained PAT (falling back to the
-          # default token) so the resulting `labeled` event retriggers the
-          # backport pre-merge check in Required Checks. Labels applied by the
-          # default GITHUB_TOKEN do not trigger downstream workflows.
+          # Label ops use the fine-grained PAT (falling back to the default
+          # token) so the resulting `labeled` event retriggers the backport
+          # pre-merge check — labels applied by the default GITHUB_TOKEN do not
+          # trigger downstream workflows. The review request is sent by the bot
+          # (GITHUB_TOKEN, via BOT_TOKEN below) so it is attributed to
+          # github-actions[bot] rather than the PAT owner; it needs no trigger.
           github-token: ${{ secrets.AUTO_MERGE_TOKEN || secrets.GITHUB_TOKEN }}
           script: |
             const entries = JSON.parse(process.env.ENTRIES || "[]");
@@ -190,7 +193,11 @@ jobs:
 
               if (manager && manager !== author) {
                 try {
-                  await github.rest.pulls.requestReviewers({
+                  // Send the review request as github-actions[bot] 
(BOT_TOKEN),
+                  // not the PAT owner — it needs no downstream trigger, so it
+                  // does not have to use the label PAT.
+                  const bot = 
require("@actions/github").getOctokit(process.env.BOT_TOKEN);
+                  await bot.rest.pulls.requestReviewers({
                     owner,
                     repo,
                     pull_number: pr.number,
diff --git a/.github/workflows/backport-checks.yml 
b/.github/workflows/backport-checks.yml
new file mode 100644
index 0000000000..7288928ef3
--- /dev/null
+++ b/.github/workflows/backport-checks.yml
@@ -0,0 +1,140 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+# Backport preflight, kept out of Required Checks so a conflicting backport is
+# never a red X on the PR and never reads like a required check failed. For
+# each release/* label:
+#   1. apply-check: a git-only dry-run cherry-pick onto the release branch.
+#   2. backport: for targets that apply cleanly, build the backported tree so
+#      "applies but does not compile on the release branch" is caught 
pre-merge.
+# Neither job fails on a bad backport — the apply outcome is written to an
+# artifact and the Backport Preflight Publish workflow (workflow_run) turns it
+# into a `backport-preflight (<target>)` check that is `neutral` (grey) on a
+# conflict and `success` on a clean apply. Publishing lives in that companion
+# because this workflow runs on `pull_request`, whose token is read-only on
+# fork PRs (renovate, external contributors) and so cannot create check runs.
+# Post-merge, Direct Backport Push reads the neutral/success check and the
+# backport build legs to decide push-straight vs. open-a-draft-PR.
+name: Backport Checks
+
+on:
+  pull_request:
+    types:
+      - opened
+      - reopened
+      - synchronize
+      - labeled
+      - unlabeled
+
+permissions:
+  checks: write
+  contents: read
+  pull-requests: read
+
+concurrency:
+  group: backport-checks-${{ github.event.pull_request.number }}
+  cancel-in-progress: true
+
+jobs:
+  # Reused so the release/* target list (and the label -> stack mapping the
+  # backport build needs) is derived exactly as the main build derives it.
+  precheck:
+    uses: ./.github/workflows/precheck.yml
+
+  # Fast git-only pre-flight: does the change even cherry-pick onto each
+  # release branch? One job loops the targets (each is seconds), records
+  # applied/conflict per target, and emits the clean-target list so a
+  # conflicting target never spins up the expensive backport build below.
+  apply-check:
+    needs: precheck
+    if: ${{ needs.precheck.outputs.backport_targets != '[]' }}
+    runs-on: ubuntu-latest
+    outputs:
+      buildable: ${{ steps.check.outputs.buildable }}
+    steps:
+      - name: Checkout PR head
+        uses: actions/checkout@v7
+        with:
+          ref: refs/pull/${{ github.event.pull_request.number }}/head
+          fetch-depth: 0
+      - name: Dry-run cherry-pick each target
+        id: check
+        env:
+          TARGETS: ${{ needs.precheck.outputs.backport_targets }}
+          RANGE: ${{ format('{0}..{1}', github.event.pull_request.base.sha, 
github.event.pull_request.head.sha) }}
+          PR_NUMBER: ${{ github.event.pull_request.number }}
+          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+        run: |
+          set -uo pipefail
+          mkdir -p preflight
+          results='[]'
+          buildable='[]'
+          for target in $(echo "$TARGETS" | jq -r '.[]'); do
+            # Clear any leftover cherry-pick from the previous target so the
+            # script's `git checkout -B` is not blocked by an unmerged index.
+            git cherry-pick --abort 2>/dev/null || true
+            git rebase --abort 2>/dev/null || true
+            if bash ./.github/scripts/prepare-backport-checkout.sh "$target" 
"$RANGE"; then
+              echo "[preflight] ${target}: applies cleanly"
+              applied=true
+              buildable=$(echo "$buildable" | jq -c --arg t "$target" '. + 
[$t]')
+            else
+              echo "[preflight] ${target}: conflicts"
+              applied=false
+            fi
+            results=$(echo "$results" | jq -c --arg t "$target" --argjson a 
"$applied" '. + [{target: $t, applied: $a}]')
+          done
+          git cherry-pick --abort 2>/dev/null || true
+          echo "buildable=${buildable}" >> "$GITHUB_OUTPUT"
+          printf '%s\n' "$results" > preflight/results.json
+          jq -nc --argjson pr "$PR_NUMBER" --arg sha "$HEAD_SHA" '{pr: $pr, 
head_sha: $sha}' > preflight/meta.json
+          echo "Results: ${results}"
+          echo "Buildable: ${buildable}"
+      - name: Upload preflight results
+        uses: actions/upload-artifact@v4
+        with:
+          name: backport-preflight
+          path: preflight/
+          retention-days: 3
+
+  # Build the backported tree for targets that applied cleanly, so a clean
+  # cherry-pick that nonetheless fails to compile on the release branch is
+  # caught before merge (Direct Backport Push reads these legs to decide
+  # push-straight vs. open-a-draft-PR). Same stack selection as the main build.
+  backport:
+    needs: [precheck, apply-check]
+    if: ${{ needs.apply-check.outputs.buildable != '' && 
needs.apply-check.outputs.buildable != '[]' }}
+    strategy:
+      fail-fast: false
+      matrix:
+        target: ${{ fromJson(needs.apply-check.outputs.buildable) }}
+    uses: ./.github/workflows/build.yml
+    with:
+      checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head
+      backport_target_branch: ${{ matrix.target }}
+      backport_commit_range: ${{ format('{0}..{1}', 
github.event.pull_request.base.sha, github.event.pull_request.head.sha) }}
+      job_name_suffix: ""
+      run_frontend: ${{ needs.precheck.outputs.run_frontend == 'true' }}
+      run_amber: ${{ needs.precheck.outputs.run_amber == 'true' }}
+      run_amber_integration: ${{ needs.precheck.outputs.run_amber_integration 
== 'true' }}
+      run_platform: ${{ needs.precheck.outputs.run_platform == 'true' }}
+      run_platform_integration: ${{ 
needs.precheck.outputs.run_platform_integration == 'true' }}
+      run_pyamber: ${{ needs.precheck.outputs.run_pyamber == 'true' }}
+      run_agent_service: ${{ needs.precheck.outputs.run_agent_service == 
'true' }}
+      run_infra: ${{ needs.precheck.outputs.run_infra == 'true' }}
+      run_pyright_language_service: ${{ 
needs.precheck.outputs.run_pyright_language_service == 'true' }}
+    secrets: inherit
diff --git a/.github/workflows/backport-publish.yml 
b/.github/workflows/backport-publish.yml
new file mode 100644
index 0000000000..fde3467d64
--- /dev/null
+++ b/.github/workflows/backport-publish.yml
@@ -0,0 +1,129 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+# Companion to Backport Checks. That workflow runs on `pull_request`, whose
+# token is read-only on fork PRs and so cannot create check runs; this one runs
+# on `workflow_run` in the base-repo context with `checks: write`, so it can.
+# It reads the preflight artifact and publishes one `backport-preflight
+# (<target>)` check per target: `success` when the change cherry-picks cleanly
+# onto that release branch, `neutral` (grey — never a red X, never a merge
+# blocker) when it conflicts. A conflicting backport is expected and handled
+# after merge by Direct Backport Push, which opens a draft backport PR.
+name: Backport Preflight Publish
+
+on:
+  workflow_run:
+    workflows:
+      - Backport Checks
+    types:
+      - completed
+
+permissions:
+  actions: read
+  checks: write
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Download preflight artifact
+        id: download
+        continue-on-error: true
+        uses: actions/download-artifact@v4
+        with:
+          name: backport-preflight
+          path: preflight
+          run-id: ${{ github.event.workflow_run.id }}
+          github-token: ${{ github.token }}
+
+      - name: Publish neutral/success preflight checks
+        uses: actions/github-script@v9
+        with:
+          script: |
+            const fs = require("fs");
+            const { owner, repo } = context.repo;
+
+            // The artifact is absent when Backport Checks requested no targets
+            // (a non-backport PR, or every release/* label removed). Fall back
+            // to the workflow_run's head SHA and an empty result set so the
+            // stale-check cleanup below still runs.
+            let results = [];
+            let headSha = context.payload.workflow_run.head_sha;
+            try {
+              results = JSON.parse(fs.readFileSync("preflight/results.json", 
"utf8"));
+            } catch (e) {
+              core.info(`No preflight results artifact (${e.code || 
e.message}); nothing to publish.`);
+            }
+            try {
+              const meta = JSON.parse(fs.readFileSync("preflight/meta.json", 
"utf8"));
+              if (meta.head_sha) headSha = meta.head_sha;
+            } catch (e) {
+              core.info("No preflight meta; using workflow_run head SHA.");
+            }
+            if (!headSha) {
+              core.warning("No head SHA resolved; skipping.");
+              return;
+            }
+
+            for (const r of results) {
+              const applied = r.applied === true;
+              await github.rest.checks.create({
+                owner,
+                repo,
+                name: `backport-preflight (${r.target})`,
+                head_sha: headSha,
+                status: "completed",
+                conclusion: applied ? "success" : "neutral",
+                output: {
+                  title: applied
+                    ? `Cherry-picks cleanly onto ${r.target}`
+                    : `Conflicts on ${r.target} — needs a manual backport`,
+                  summary: applied
+                    ? `The change applies cleanly onto \`${r.target}\`. If 
this PR merges it is cherry-picked straight to the release branch.`
+                    : `The change does not apply cleanly onto \`${r.target}\`. 
This is advisory and does not block merging: after merge, Direct Backport Push 
opens a draft backport PR for manual conflict resolution. Remove the 
\`${r.target}\` label to decline the backport.`,
+                },
+              });
+              core.info(`Published backport-preflight (${r.target}) = 
${applied ? "success" : "neutral"}.`);
+            }
+
+            // Cancel stale preflight checks for targets no longer requested
+            // (e.g. a release/* label was removed): a leftover grey/green mark
+            // for a target that is gone is misleading.
+            const current = new Set(results.map((r) => r.target));
+            const checks = await 
github.paginate(github.rest.checks.listForRef, {
+              owner,
+              repo,
+              ref: headSha,
+              per_page: 100,
+            });
+            for (const check of checks) {
+              const m = check.name.match(/^backport-preflight \((.+)\)$/);
+              if (!m || current.has(m[1])) continue;
+              if (check.status === "completed" && check.conclusion === 
"cancelled") continue;
+              try {
+                await github.rest.checks.update({
+                  owner,
+                  repo,
+                  check_run_id: check.id,
+                  status: "completed",
+                  conclusion: "cancelled",
+                });
+                core.info(`Cancelled stale ${check.name}.`);
+              } catch (e) {
+                core.warning(`Failed to cancel ${check.name}: ${e.message}`);
+              }
+            }
diff --git a/.github/workflows/direct-backport-push.yml 
b/.github/workflows/direct-backport-push.yml
index 58031e26e1..2ce8815e70 100644
--- a/.github/workflows/direct-backport-push.yml
+++ b/.github/workflows/direct-backport-push.yml
@@ -23,6 +23,7 @@ on:
 
 permissions:
   actions: read
+  checks: read
   contents: write
   issues: write
   pull-requests: write
@@ -147,33 +148,53 @@ jobs:
               return null;
             }
 
-            // Collect every job across the PR head's Required Checks runs so
-            // both the pre-flight apply-check and the full backport build can
-            // be inspected per target.
+            // Collect the backport build legs for the PR head. They run in
+            // Backport Checks now; during rollout, older PRs still have their
+            // legs (and the old apply-check job) in Required Checks, so gather
+            // jobs from both workflows.
             async function jobsForPr(pullRequest) {
-              const buildRuns = await github.paginate(
-                github.rest.actions.listWorkflowRuns,
-                {
+              const workflowFiles = ["backport-checks.yml", 
"required-checks.yml"];
+              const allJobs = [];
+              for (const wf of workflowFiles) {
+                let runs = [];
+                try {
+                  runs = await 
github.paginate(github.rest.actions.listWorkflowRuns, {
+                    owner,
+                    repo,
+                    workflow_id: wf,
+                    head_sha: pullRequest.head.sha,
+                    per_page: 100,
+                  });
+                } catch (e) {
+                  core.info(`No ${wf} runs for ${pullRequest.head.sha} 
(${e.status ?? "?"}).`);
+                  continue;
+                }
+                for (const run of runs) {
+                  const jobs = await github.paginate(
+                    github.rest.actions.listJobsForWorkflowRun,
+                    { owner, repo, run_id: run.id, per_page: 100 }
+                  );
+                  allJobs.push(...jobs);
+                }
+              }
+              return allJobs;
+            }
+
+            // The apply-check outcome is now a published check run,
+            // `backport-preflight (<target>)`: success = clean, neutral =
+            // conflict. Read the PR head's check runs to find them.
+            async function checkRunsForPr(pullRequest) {
+              try {
+                return await github.paginate(github.rest.checks.listForRef, {
                   owner,
                   repo,
-                  workflow_id: "required-checks.yml",
-                  head_sha: pullRequest.head.sha,
+                  ref: pullRequest.head.sha,
                   per_page: 100,
-                }
-              );
-              if (buildRuns.length === 0) {
-                core.warning(`No Required Checks runs found for 
${pullRequest.head.sha}.`);
+                });
+              } catch (e) {
+                core.warning(`listForRef ${pullRequest.head.sha} failed: 
${e.message}`);
                 return [];
               }
-              const allJobs = [];
-              for (const run of buildRuns) {
-                const jobs = await github.paginate(
-                  github.rest.actions.listJobsForWorkflowRun,
-                  { owner, repo, run_id: run.id, per_page: 100 }
-                );
-                allJobs.push(...jobs);
-              }
-              return allJobs;
             }
 
             const isDone = (job) => job.status === "completed";
@@ -193,10 +214,17 @@ jobs:
             //          job could push a conflict or open a spurious PR, so we
             //          leave it (the scheduled reconciliation, if any, or a
             //          re-push will pick it up once checks are done).
-            function classifyTargets(allJobs, requestedTargets) {
+            function classifyTargets(allJobs, checkRuns, requestedTargets) {
               const push = [];
               const pr = [];
               for (const target of requestedTargets) {
+                // Preferred apply signal: the published preflight check
+                // (success = clean, neutral = conflict).
+                const applyCheck = checkRuns.find(
+                  (c) => c.name === `backport-preflight (${target})`
+                );
+                // Rollout fallback: PRs whose last run predates the split 
still
+                // expose the old red/green apply-check job in Required Checks.
                 const applyJob = allJobs.find(
                   (job) => job.name === `backport-apply-check (${target})`
                 );
@@ -204,7 +232,16 @@ jobs:
                   job.name.startsWith(`backport (${target}) / `)
                 );
 
-                if (applyJob && isDone(applyJob) && !isGreen(applyJob)) {
+                // Resolve the apply signal into clean | conflict | unknown.
+                let apply = "unknown";
+                if (applyCheck && applyCheck.status === "completed") {
+                  if (applyCheck.conclusion === "success") apply = "clean";
+                  else if (applyCheck.conclusion === "neutral") apply = 
"conflict";
+                } else if (applyJob && isDone(applyJob)) {
+                  apply = isGreen(applyJob) ? "clean" : "conflict";
+                }
+
+                if (apply === "conflict") {
                   pr.push(target); // cherry-pick conflicts
                   continue;
                 }
@@ -213,12 +250,11 @@ jobs:
                   continue;
                 }
 
-                const applyOk = applyJob && isDone(applyJob) && 
isGreen(applyJob);
                 const anyBuildDone = buildJobs.some(isDone);
-                // Normal path: apply-check finished green. The `!applyJob`
-                // clause keeps older runs (before apply-check existed) working
-                // off the build legs alone during rollout.
-                if (applyOk || (!applyJob && anyBuildDone)) {
+                // Normal path: apply finished clean. The `unknown` + 
build-legs
+                // clause keeps older runs (before the apply signal existed)
+                // working off the build legs alone during rollout.
+                if (apply === "clean" || (apply === "unknown" && 
anyBuildDone)) {
                   push.push(target);
                 } else {
                   core.warning(`PR: no completed backport signal for 
${target}; skipping.`);
@@ -272,7 +308,8 @@ jobs:
               }
 
               const allJobs = await jobsForPr(pullRequest);
-              const { push, pr } = classifyTargets(allJobs, requestedTargets);
+              const checkRuns = await checkRunsForPr(pullRequest);
+              const { push, pr } = classifyTargets(allJobs, checkRuns, 
requestedTargets);
               core.info(
                 `PR #${pullRequest.number}: push=[${push.join(", ")}] 
pr=[${pr.join(", ")}]`
               );
@@ -788,7 +825,12 @@ jobs:
           SUBJECT: ${{ steps.branch.outputs.subject }}
           FEATURE_ABSENT: ${{ steps.branch.outputs.feature_absent }}
         with:
-          github-token: ${{ secrets.AUTO_MERGE_TOKEN || secrets.GITHUB_TOKEN }}
+          # Open the draft as github-actions[bot], not the PAT owner, by using
+          # the default GITHUB_TOKEN. Trade-off: a GITHUB_TOKEN-opened PR does
+          # not trigger pull_request CI — acceptable because this is a draft 
for
+          # manual conflict/build resolution, so CI fires once the human pushes
+          # their fix to the branch.
+          github-token: ${{ secrets.GITHUB_TOKEN }}
           script: |
             const {
               MERGE_SHA, TARGET_BRANCH, PR_NUMBER, MANAGER,
@@ -834,8 +876,12 @@ jobs:
             // the backport PR reads e.g. `fix(amber, v1.2): ...`. Falls back 
to
             // suffixing the version if the subject is not conventional.
             function backportTitle(subject, version) {
-              const m = subject.match(/^(\w+)(?:\(([^)]*)\))?(!)?:\s*(.*)$/);
-              if (!m) return `${subject} (${version})`;
+              // Drop the trailing "(#N)" GitHub appends to the squash subject
+              // so the backport title does not render as a link back to the
+              // original PR.
+              const clean = subject.replace(/\s*\(#\d+\)\s*$/, "");
+              const m = clean.match(/^(\w+)(?:\(([^)]*)\))?(!)?:\s*(.*)$/);
+              if (!m) return `${clean} (${version})`;
               const [, type, scope, bang, rest] = m;
               const newScope = scope ? `${scope}, ${version}` : version;
               return `${type}(${newScope})${bang || ""}: ${rest}`;
@@ -855,17 +901,84 @@ jobs:
               const conflictList = CONFLICT_FILES.trim()
                 ? CONFLICT_FILES.trim().split(/\s+/).map((f) => `- 
\`${f}\``).join("\n")
                 : "";
+              const statusLine = hadConflict
+                ? `The cherry-pick **conflicted** and was committed with 
conflict markers. ` +
+                  `Resolve the conflicts on this branch, then mark this PR 
ready for review.`
+                : `The cherry-pick applied cleanly but the backported tree 
failed its ` +
+                  `pre-merge build. Fix the build on this branch, then mark 
this PR ready for review.`;
+
+              // Carry the original PR's linked issues (its GitHub-linked
+              // closing references) and any discussion / mailing-list links
+              // into the backport so the release-branch reviewer has the same
+              // context. Plain `#N` mentions, never `Closes` — the backport
+              // must not re-close an issue the original PR already resolved.
+              let relatedSection = `Backport of #${prNumber}.`;
+              try {
+                const linked = await github.graphql(
+                  `query ($owner: String!, $repo: String!, $num: Int!) {
+                    repository(owner: $owner, name: $repo) {
+                      pullRequest(number: $num) {
+                        body
+                        closingIssuesReferences(first: 20) { nodes { number } }
+                      }
+                    }
+                  }`,
+                  { owner, repo, num: prNumber }
+                );
+                const original = linked.repository.pullRequest;
+                const bodyText = original.body || "";
+                const issues = (original.closingIssuesReferences?.nodes || 
[]).map(
+                  (n) => n.number
+                );
+                const discussions = [
+                  ...bodyText.matchAll(
+                    
/https:\/\/(?:github\.com\/[^\s)]+\/discussions\/\d+|lists\.apache\.org\/[^\s)]+)/g
+                  ),
+                ].map((m) => m[0]);
+                // Security dependency PRs (e.g. Renovate) usually link no 
issue;
+                // fall back to the CVE / GHSA advisories named in the body so
+                // the backport still points at what it fixes.
+                const cves = [
+                  ...new 
Set([...bodyText.matchAll(/\bCVE-\d{4}-\d{4,7}\b/g)].map((m) => m[0])),
+                ];
+                const ghsas = [
+                  ...new Set(
+                    
[...bodyText.matchAll(/\bGHSA(?:-[a-z0-9]{4}){3}\b/gi)].map((m) => m[0])
+                  ),
+                ];
+                const advisories = cves.length ? cves : ghsas;
+                const parts = [`Backport of #${prNumber}.`];
+                if (issues.length) {
+                  parts.push(`Originally linked ${issues.map((n) => 
`#${n}`).join(", ")}.`);
+                } else if (advisories.length) {
+                  parts.push(`Fixes ${advisories.join(", ")} (see #${prNumber} 
for details).`);
+                }
+                parts.push(...discussions);
+                relatedSection = parts.join(" ");
+              } catch (e) {
+                core.warning(`Could not read linked issues from #${prNumber}: 
${e.message}`);
+              }
+
               const body = [
-                `Automated backport of #${prNumber} to \`${TARGET_BRANCH}\`.`,
+                `### What changes were proposed in this PR?`,
                 ``,
-                hadConflict
-                  ? `⚠️ The cherry-pick **conflicted** and was committed with 
conflict markers. ` +
-                    `Resolve the conflicts on this branch, then mark the PR 
ready for review.`
-                  : `The cherry-pick applied cleanly but the backported tree 
failed its ` +
-                    `pre-merge build. Fix the build on this branch, then mark 
the PR ready for review.`,
-                conflictList ? `\n**Conflicting files:**\n${conflictList}` : 
``,
+                `Automated backport of #${prNumber} to \`${TARGET_BRANCH}\`.`,
                 ``,
                 `Source: ${MERGE_SHA} · [automation run](${runUrl})`,
+                ``,
+                `### Any related issues, documentation, discussions?`,
+                ``,
+                relatedSection,
+                ``,
+                `### How was this PR tested?`,
+                ``,
+                `Release-branch CI runs on this branch once the ` +
+                  `${hadConflict ? "conflicts are resolved" : "build is 
fixed"} and ` +
+                  `this PR is marked ready for review.`,
+                ``,
+                `### Was this PR authored or co-authored using generative AI 
tooling?`,
+                ``,
+                `No.`,
               ].join("\n");
 
               pr = (await github.rest.pulls.create({
@@ -873,6 +986,19 @@ jobs:
                 title, body, draft: true,
               })).data;
               core.info(`Opened draft backport PR #${pr.number}.`);
+
+              // Post the how-to-finish instructions as a comment on the new
+              // backport PR (kept out of the templated body).
+              const statusComment = conflictList
+                ? `${statusLine}\n\n**Conflicting files:**\n${conflictList}`
+                : statusLine;
+              try {
+                await github.rest.issues.createComment({
+                  owner, repo, issue_number: pr.number, body: statusComment,
+                });
+              } catch (e) {
+                core.warning(`Could not comment on backport PR #${pr.number}: 
${e.message}`);
+              }
             }
 
             // Assign the original author (best-effort: external authors may 
not
@@ -906,13 +1032,17 @@ jobs:
 
             // Link the backport PR back from the original PR so it is not 
lost.
             const prUrl = 
`${context.serverUrl}/${owner}/${repo}/pull/${pr.number}`;
+            const reason = hadConflict
+              ? "the cherry-pick conflicts"
+              : "the backported tree failed its pre-merge build";
             try {
               await github.rest.issues.createComment({
                 owner, repo, issue_number: prNumber,
                 body:
-                  `Backport to \`${TARGET_BRANCH}\` needs manual work — opened 
` +
-                  `draft PR #${pr.number} (${prUrl})` +
-                  (author ? `, assigned to @${author}` : "") + `.`,
+                  `Backport PR opened: draft #${pr.number} (${prUrl}) to ` +
+                  `\`${TARGET_BRANCH}\`` +
+                  (author ? `, assigned to @${author}` : "") +
+                  ` — needs manual work because ${reason}.`,
               });
             } catch (e) {
               core.warning(`Could not comment on #${prNumber}: ${e.message}`);
diff --git a/.github/workflows/required-checks.yml 
b/.github/workflows/precheck.yml
similarity index 55%
copy from .github/workflows/required-checks.yml
copy to .github/workflows/precheck.yml
index 6194d621cd..c98589060b 100644
--- a/.github/workflows/required-checks.yml
+++ b/.github/workflows/precheck.yml
@@ -15,47 +15,45 @@
 # specific language governing permissions and limitations
 # under the License.
 
-name: Required Checks
+# Reusable pre-flight that both Required Checks (main build) and Backport
+# Checks (backport preflight/build) call, so the label -> stack mapping and
+# the labeler-wait live in exactly one place. On PR events it waits for the
+# Pull Request Labeler to finish, then maps the PR's labels to the build
+# stacks each requires and to the release/* backport targets. Non-PR events
+# (push / merge_group / workflow_dispatch) run every stack and request no
+# backports.
+name: Precheck
 
 on:
-  push:
-    branches:
-      - 'ci-enable/**'
-      - 'main'
-      - 'release/**'
-  pull_request:
-    types:
-      - opened
-      - reopened
-      - synchronize
-      - labeled
-      - unlabeled
-  merge_group:
-  workflow_dispatch:
+  workflow_call:
+    outputs:
+      run_frontend:
+        value: ${{ jobs.decide.outputs.run_frontend }}
+      run_amber:
+        value: ${{ jobs.decide.outputs.run_amber }}
+      run_amber_integration:
+        value: ${{ jobs.decide.outputs.run_amber_integration }}
+      run_platform:
+        value: ${{ jobs.decide.outputs.run_platform }}
+      run_platform_integration:
+        value: ${{ jobs.decide.outputs.run_platform_integration }}
+      run_pyamber:
+        value: ${{ jobs.decide.outputs.run_pyamber }}
+      run_agent_service:
+        value: ${{ jobs.decide.outputs.run_agent_service }}
+      run_infra:
+        value: ${{ jobs.decide.outputs.run_infra }}
+      run_pyright_language_service:
+        value: ${{ jobs.decide.outputs.run_pyright_language_service }}
+      backport_targets:
+        value: ${{ jobs.decide.outputs.backport_targets }}
 
 permissions:
-  checks: write
-  contents: read
+  checks: read
   pull-requests: read
 
-concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: ${{ github.ref != 'refs/heads/main' && 
!startsWith(github.ref, 'refs/heads/release/') }}
-
 jobs:
-  # Precheck decides which downstream jobs run for this event:
-  #   - On PR events, wait for the Pull Request Labeler workflow to finish so
-  #     the labels it applies (frontend, docs, infra, ...) are available, then
-  #     gate run_* outputs on those labels.
-  #   - run_frontend / run_amber / run_platform / run_pyamber /
-  #     run_agent_service: gate the main build stacks. Each labeler-applied
-  #     label maps to the stacks it requires (LABEL_STACKS below); the run
-  #     set is the union across all PR labels. Empty union (e.g. docs-only
-  #     PRs) skips every stack. Push and workflow_dispatch
-  #     events run every stack.
-  #   - backport_targets: JSON array of release/* labels currently on the PR.
-  #     Drives the backport matrix; empty array means no backport runs.
-  precheck:
+  decide:
     name: Precheck
     runs-on: ubuntu-latest
     outputs:
@@ -231,167 +229,3 @@ jobs:
               core.info(`Backport targets: ${targets.join(", ")}`);
             }
             core.setOutput("backport_targets", JSON.stringify(targets));
-
-  cleanup-stale-backport:
-    if: ${{ github.event_name == 'pull_request' && github.event.action == 
'unlabeled' && startsWith(github.event.label.name, 'release/') }}
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cancel obsolete backport check_runs for the removed target
-        uses: actions/github-script@v9
-        with:
-          script: |
-            const { owner, repo } = context.repo;
-            const target = context.payload.label.name;
-            const headSha = context.payload.pull_request.head.sha;
-            const prefix = `backport (${target}) `;
-
-            const checks = await github.paginate(
-              github.rest.checks.listForRef,
-              { owner, repo, ref: headSha, per_page: 100 }
-            );
-
-            for (const check of checks) {
-              if (!check.name.startsWith(prefix)) continue;
-              if (check.status === "completed" && check.conclusion === 
"cancelled") continue;
-              try {
-                await github.rest.checks.update({
-                  owner,
-                  repo,
-                  check_run_id: check.id,
-                  status: "completed",
-                  conclusion: "cancelled",
-                });
-                core.info(`Cancelled check ${check.name}`);
-              } catch (e) {
-                core.warning(`Failed to update check ${check.id} 
(${check.name}): ${e.message}`);
-              }
-            }
-
-  build:
-    needs: precheck
-    uses: ./.github/workflows/build.yml
-    with:
-      run_frontend: ${{ needs.precheck.outputs.run_frontend == 'true' }}
-      run_amber: ${{ needs.precheck.outputs.run_amber == 'true' }}
-      run_amber_integration: ${{ needs.precheck.outputs.run_amber_integration 
== 'true' }}
-      run_platform: ${{ needs.precheck.outputs.run_platform == 'true' }}
-      run_platform_integration: ${{ 
needs.precheck.outputs.run_platform_integration == 'true' }}
-      run_pyamber: ${{ needs.precheck.outputs.run_pyamber == 'true' }}
-      run_agent_service: ${{ needs.precheck.outputs.run_agent_service == 
'true' }}
-      run_infra: ${{ needs.precheck.outputs.run_infra == 'true' }}
-      run_pyright_language_service: ${{ 
needs.precheck.outputs.run_pyright_language_service == 'true' }}
-    secrets: inherit
-
-  # Fast pre-flight: does the change even cherry-pick onto each release branch?
-  # This is a git-only job (no build), so a conflict is reported in seconds and
-  # gates the expensive backport build below — a conflicting target never spins
-  # up the full stack matrix. Post-merge, Direct Backport Push reads this job's
-  # result as the authoritative "conflicts / applies" signal.
-  backport-apply-check:
-    needs: precheck
-    if: ${{ needs.precheck.outputs.backport_targets != '[]' }}
-    strategy:
-      fail-fast: false
-      matrix:
-        target: ${{ fromJson(needs.precheck.outputs.backport_targets) }}
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout PR head
-        uses: actions/checkout@v7
-        with:
-          ref: refs/pull/${{ github.event.pull_request.number }}/head
-          fetch-depth: 0
-      - name: Dry-run cherry-pick onto ${{ matrix.target }}
-        run: |
-          bash ./.github/scripts/prepare-backport-checkout.sh \
-            "${{ matrix.target }}" \
-            "${{ format('{0}..{1}', github.event.pull_request.base.sha, 
github.event.pull_request.head.sha) }}"
-
-  # Collect the targets whose pre-flight cherry-pick succeeded. always() so a
-  # conflict on one target still lets the others build; an all-conflict run
-  # yields an empty list and skips the backport build entirely.
-  backport-buildable:
-    needs: [precheck, backport-apply-check]
-    if: ${{ always() && needs.precheck.outputs.backport_targets != '[]' }}
-    runs-on: ubuntu-latest
-    outputs:
-      targets: ${{ steps.collect.outputs.targets }}
-    steps:
-      - name: Collect targets that applied cleanly
-        id: collect
-        uses: actions/github-script@v9
-        env:
-          REQUESTED: ${{ needs.precheck.outputs.backport_targets }}
-        with:
-          script: |
-            const requested = JSON.parse(process.env.REQUESTED || "[]");
-            const { owner, repo } = context.repo;
-            const jobs = await github.paginate(
-              github.rest.actions.listJobsForWorkflowRun,
-              { owner, repo, run_id: context.runId, per_page: 100 }
-            );
-            const buildable = requested.filter((target) => {
-              const job = jobs.find(
-                (j) => j.name === `backport-apply-check (${target})`
-              );
-              return job && job.conclusion === "success";
-            });
-            core.info(`Buildable backport targets: 
${JSON.stringify(buildable)}`);
-            core.setOutput("targets", JSON.stringify(buildable));
-
-  backport:
-    needs: [precheck, backport-buildable]
-    if: ${{ needs.backport-buildable.outputs.targets != '' && 
needs.backport-buildable.outputs.targets != '[]' }}
-    strategy:
-      fail-fast: false
-      matrix:
-        target: ${{ fromJson(needs.backport-buildable.outputs.targets) }}
-    uses: ./.github/workflows/build.yml
-    with:
-      checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head
-      backport_target_branch: ${{ matrix.target }}
-      backport_commit_range: ${{ format('{0}..{1}', 
github.event.pull_request.base.sha, github.event.pull_request.head.sha) }}
-      job_name_suffix: ""
-      run_frontend: ${{ needs.precheck.outputs.run_frontend == 'true' }}
-      run_amber: ${{ needs.precheck.outputs.run_amber == 'true' }}
-      run_amber_integration: ${{ needs.precheck.outputs.run_amber_integration 
== 'true' }}
-      run_platform: ${{ needs.precheck.outputs.run_platform == 'true' }}
-      run_platform_integration: ${{ 
needs.precheck.outputs.run_platform_integration == 'true' }}
-      run_pyamber: ${{ needs.precheck.outputs.run_pyamber == 'true' }}
-      run_agent_service: ${{ needs.precheck.outputs.run_agent_service == 
'true' }}
-      run_infra: ${{ needs.precheck.outputs.run_infra == 'true' }}
-      run_pyright_language_service: ${{ 
needs.precheck.outputs.run_pyright_language_service == 'true' }}
-    secrets: inherit
-
-  required-checks:
-    # Do not rename this job — its display name is referenced in .asf.yaml.
-    name: Required Checks
-    # The backport job is deliberately NOT a dependency here: it is an advisory
-    # signal, not a merge gate. A red pre-merge backport (a cherry-pick that
-    # conflicts, or a backported tree that fails to build) must not block the
-    # PR from landing on main — instead Direct Backport Push opens a draft
-    # backport PR for the author after merge. Keeping backport out of `needs`
-    # also lets this aggregator report as soon as build finishes.
-    needs: [precheck, build]
-    if: always()
-    runs-on: ubuntu-latest
-    steps:
-      - name: Verify all required checks succeeded or were skipped
-        run: |
-          declare -A results=(
-            [precheck]="${{ needs.precheck.result }}"
-            [build]="${{ needs.build.result }}"
-          )
-          failed=0
-          for job in "${!results[@]}"; do
-            r="${results[$job]}"
-            echo "${job}: ${r}"
-            if [[ "$r" != "success" && "$r" != "skipped" ]]; then
-              failed=1
-            fi
-          done
-          if (( failed )); then
-            echo "::error::One or more required checks did not succeed."
-            exit 1
-          fi
-          echo "All required checks succeeded or were skipped."
diff --git a/.github/workflows/required-checks.yml 
b/.github/workflows/required-checks.yml
index 6194d621cd..f2ee2efb89 100644
--- a/.github/workflows/required-checks.yml
+++ b/.github/workflows/required-checks.yml
@@ -43,229 +43,13 @@ concurrency:
   cancel-in-progress: ${{ github.ref != 'refs/heads/main' && 
!startsWith(github.ref, 'refs/heads/release/') }}
 
 jobs:
-  # Precheck decides which downstream jobs run for this event:
-  #   - On PR events, wait for the Pull Request Labeler workflow to finish so
-  #     the labels it applies (frontend, docs, infra, ...) are available, then
-  #     gate run_* outputs on those labels.
-  #   - run_frontend / run_amber / run_platform / run_pyamber /
-  #     run_agent_service: gate the main build stacks. Each labeler-applied
-  #     label maps to the stacks it requires (LABEL_STACKS below); the run
-  #     set is the union across all PR labels. Empty union (e.g. docs-only
-  #     PRs) skips every stack. Push and workflow_dispatch
-  #     events run every stack.
-  #   - backport_targets: JSON array of release/* labels currently on the PR.
-  #     Drives the backport matrix; empty array means no backport runs.
+  # Decide which build stacks this event needs (label union on PR events; all
+  # stacks on push/merge_group/dispatch). Shared with Backport Checks via the
+  # reusable precheck workflow so the label -> stack mapping lives in one place
+  # (.github/workflows/precheck.yml). The backport_targets output is unused
+  # here — backport preflight/build moved to the Backport Checks workflow.
   precheck:
-    name: Precheck
-    runs-on: ubuntu-latest
-    outputs:
-      run_frontend: ${{ steps.decide.outputs.run_frontend }}
-      run_amber: ${{ steps.decide.outputs.run_amber }}
-      run_amber_integration: ${{ steps.decide.outputs.run_amber_integration }}
-      run_platform: ${{ steps.decide.outputs.run_platform }}
-      run_platform_integration: ${{ 
steps.decide.outputs.run_platform_integration }}
-      run_pyamber: ${{ steps.decide.outputs.run_pyamber }}
-      run_agent_service: ${{ steps.decide.outputs.run_agent_service }}
-      run_infra: ${{ steps.decide.outputs.run_infra }}
-      run_pyright_language_service: ${{ 
steps.decide.outputs.run_pyright_language_service }}
-      backport_targets: ${{ steps.decide.outputs.backport_targets }}
-    steps:
-      - name: Wait for Pull Request Labeler
-        if: github.event_name == 'pull_request'
-        uses: actions/github-script@v9
-        with:
-          script: |
-            const ref = context.payload.pull_request.head.sha;
-            const maxAttempts = 30;
-            for (let i = 0; i < maxAttempts; i++) {
-              const { data } = await github.rest.checks.listForRef({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                ref,
-                check_name: "labeler",
-              });
-              const check = data.check_runs[0];
-              if (check && check.status === "completed") {
-                core.info(`labeler ${check.conclusion}`);
-                return;
-              }
-              core.info(`labeler not ready (attempt ${i + 1}/${maxAttempts})`);
-              await new Promise((r) => setTimeout(r, 10000));
-            }
-            core.warning("labeler did not complete within 5 minutes; 
proceeding with current labels.");
-
-      - name: Decide which jobs to run
-        id: decide
-        uses: actions/github-script@v9
-        with:
-          script: |
-            const eventName = context.eventName;
-            let labels = [];
-
-            if (eventName === "pull_request") {
-              // Re-fetch labels: the labeler may have just added some.
-              const { data: pr } = await github.rest.pulls.get({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                pull_number: context.payload.pull_request.number,
-              });
-              labels = pr.labels.map((l) => l.name);
-              core.info(`PR labels: ${labels.join(", ") || "(none)"}`);
-            }
-
-            // Map each labeler-applied label to the stacks it requires. The
-            // run set is the union across all PR labels. Labels not listed
-            // here (release/*, feature, fix, refactor) contribute no stacks.
-            // dependencies is intentionally a no-op: every dep manifest the
-            // labeler matches lives under a component dir and is already
-            // covered by that component's label — including
-            // pyright-language-service/package.json, kept honest by the
-            // `pyright-language-service` component label below.
-            //
-            // `infra` and `pyright-language-service` are 1:1 self-mapping
-            // labels (each fires only its own like-named stack); they are
-            // omitted from the illustrative table below to keep it narrow.
-            //
-            // `platform-integration` mirrors `amber-integration`: a dedicated
-            // `platform-integration` label fires only the integration stack
-            // (a spec-only change) without the full `platform` job. It is
-            // omitted from the table below to keep it narrow. No labeler rule
-            // applies it yet — platform has no integration specs (#6047) — so
-            // today it is a forward hook (applied manually until then).
-            //
-            //   label             | frontend | amber | amber-integ | platform 
| pyamber | agent-service
-            //   
------------------|----------|-------|-------------|----------|---------|--------------
-            //   frontend          |    x     |       |             |          
|         |
-            //   pyamber           |          |       |      x      |          
|    x    |
-            //   engine            |          |   x   |      x      |          
|         |
-            //   amber-integration |          |       |      x      |          
|         |
-            //   platform          |          |       |             |    x     
|         |
-            //   agent-service     |          |       |             |          
|         |     x
-            //   common            |          |   x   |      x      |    x     
|         |  (root
-            //                                                                 
            scala
-            //                                                                 
            build/lint
-            //                                                                 
            config)
-            //   ddl-change        |          |   x   |      x      |    x     
|         |
-            //   ci                |    x     |   x   |      x      |    x     
|    x    |     x
-            //   docs / deps /     |          |       |             |          
|         |
-            //   release/*         |          |       |             |          
|         |
-            //   * / branch        |          |       |             |          
|         |
-            //
-            // amber-integration runs the Scala tests tagged
-            // @org.apache.texera.amber.tags.IntegrationTest (e2e specs that
-            // spawn Python UDF workers). The labeler attaches `pyamber` to
-            // any *.py change (including amber/src/{main,test}/python/**),
-            // so `engine` does not need to fire the pyamber stack itself —
-            // pure-Python amber changes pick up `pyamber` directly. The
-            // `amber-integration` label catches *IntegrationSpec.scala
-            // edits so a test-only change does not trigger the full
-            // Scala-only amber stack.
-            const LABEL_STACKS = {
-              frontend: ["frontend"],
-              pyamber: ["amber-integration", "pyamber"],
-              engine: ["amber", "amber-integration"],
-              "amber-integration": ["amber-integration"],
-              platform: ["platform", "platform-integration"],
-              "platform-integration": ["platform-integration"],
-              "agent-service": ["agent-service"],
-              "pyright-language-service": ["pyright-language-service"],
-              common: ["amber", "amber-integration", "platform", 
"platform-integration"],
-              "ddl-change": ["amber", "amber-integration", "platform", 
"platform-integration"],
-              infra: ["infra"],
-              ci: ["frontend", "amber", "amber-integration", "platform", 
"platform-integration", "pyamber", "agent-service", "infra", 
"pyright-language-service"],
-            };
-
-            let runFrontend = true;
-            let runAmber = true;
-            let runAmberIntegration = true;
-            let runPlatform = true;
-            let runPlatformIntegration = true;
-            let runPyamber = true;
-            let runAgentService = true;
-            let runInfra = true;
-            let runPyrightLanguageService = true;
-
-            if (eventName === "pull_request") {
-              const stacks = new Set();
-              for (const label of labels) {
-                for (const stack of LABEL_STACKS[label] || []) {
-                  stacks.add(stack);
-                }
-              }
-              runFrontend = stacks.has("frontend");
-              runAmber = stacks.has("amber");
-              runAmberIntegration = stacks.has("amber-integration");
-              runPlatform = stacks.has("platform");
-              runPlatformIntegration = stacks.has("platform-integration");
-              runPyamber = stacks.has("pyamber");
-              runAgentService = stacks.has("agent-service");
-              runInfra = stacks.has("infra");
-              runPyrightLanguageService = 
stacks.has("pyright-language-service");
-              core.info(
-                `Stacks selected by label union: ${[...stacks].sort().join(", 
") || "(none)"}`
-              );
-            }
-
-            core.setOutput("run_frontend", runFrontend ? "true" : "false");
-            core.setOutput("run_amber", runAmber ? "true" : "false");
-            core.setOutput("run_amber_integration", runAmberIntegration ? 
"true" : "false");
-            core.setOutput("run_platform", runPlatform ? "true" : "false");
-            core.setOutput("run_platform_integration", runPlatformIntegration 
? "true" : "false");
-            core.setOutput("run_pyamber", runPyamber ? "true" : "false");
-            core.setOutput("run_agent_service", runAgentService ? "true" : 
"false");
-            core.setOutput("run_infra", runInfra ? "true" : "false");
-            core.setOutput("run_pyright_language_service", 
runPyrightLanguageService ? "true" : "false");
-
-            // Backport targets: all current release/* labels on the PR, unless
-            // `no-backport-needed` vetoes backporting entirely (e.g. a fix for
-            // a feature that does not exist on any release branch). The veto
-            // wins even if release/* labels are also present, so adding it
-            // mid-review cancels the backport apply-check/build on the re-run.
-            let targets = [...new Set(labels.filter((n) => 
/^release\/.+$/.test(n)))].sort();
-            if (labels.includes("no-backport-needed")) {
-              core.info("no-backport-needed present; skipping all backport 
targets.");
-              targets = [];
-            } else if (targets.length === 0) {
-              core.info("No backport targets on PR.");
-            } else {
-              core.info(`Backport targets: ${targets.join(", ")}`);
-            }
-            core.setOutput("backport_targets", JSON.stringify(targets));
-
-  cleanup-stale-backport:
-    if: ${{ github.event_name == 'pull_request' && github.event.action == 
'unlabeled' && startsWith(github.event.label.name, 'release/') }}
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cancel obsolete backport check_runs for the removed target
-        uses: actions/github-script@v9
-        with:
-          script: |
-            const { owner, repo } = context.repo;
-            const target = context.payload.label.name;
-            const headSha = context.payload.pull_request.head.sha;
-            const prefix = `backport (${target}) `;
-
-            const checks = await github.paginate(
-              github.rest.checks.listForRef,
-              { owner, repo, ref: headSha, per_page: 100 }
-            );
-
-            for (const check of checks) {
-              if (!check.name.startsWith(prefix)) continue;
-              if (check.status === "completed" && check.conclusion === 
"cancelled") continue;
-              try {
-                await github.rest.checks.update({
-                  owner,
-                  repo,
-                  check_run_id: check.id,
-                  status: "completed",
-                  conclusion: "cancelled",
-                });
-                core.info(`Cancelled check ${check.name}`);
-              } catch (e) {
-                core.warning(`Failed to update check ${check.id} 
(${check.name}): ${e.message}`);
-              }
-            }
+    uses: ./.github/workflows/precheck.yml
 
   build:
     needs: precheck
@@ -282,96 +66,9 @@ jobs:
       run_pyright_language_service: ${{ 
needs.precheck.outputs.run_pyright_language_service == 'true' }}
     secrets: inherit
 
-  # Fast pre-flight: does the change even cherry-pick onto each release branch?
-  # This is a git-only job (no build), so a conflict is reported in seconds and
-  # gates the expensive backport build below — a conflicting target never spins
-  # up the full stack matrix. Post-merge, Direct Backport Push reads this job's
-  # result as the authoritative "conflicts / applies" signal.
-  backport-apply-check:
-    needs: precheck
-    if: ${{ needs.precheck.outputs.backport_targets != '[]' }}
-    strategy:
-      fail-fast: false
-      matrix:
-        target: ${{ fromJson(needs.precheck.outputs.backport_targets) }}
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout PR head
-        uses: actions/checkout@v7
-        with:
-          ref: refs/pull/${{ github.event.pull_request.number }}/head
-          fetch-depth: 0
-      - name: Dry-run cherry-pick onto ${{ matrix.target }}
-        run: |
-          bash ./.github/scripts/prepare-backport-checkout.sh \
-            "${{ matrix.target }}" \
-            "${{ format('{0}..{1}', github.event.pull_request.base.sha, 
github.event.pull_request.head.sha) }}"
-
-  # Collect the targets whose pre-flight cherry-pick succeeded. always() so a
-  # conflict on one target still lets the others build; an all-conflict run
-  # yields an empty list and skips the backport build entirely.
-  backport-buildable:
-    needs: [precheck, backport-apply-check]
-    if: ${{ always() && needs.precheck.outputs.backport_targets != '[]' }}
-    runs-on: ubuntu-latest
-    outputs:
-      targets: ${{ steps.collect.outputs.targets }}
-    steps:
-      - name: Collect targets that applied cleanly
-        id: collect
-        uses: actions/github-script@v9
-        env:
-          REQUESTED: ${{ needs.precheck.outputs.backport_targets }}
-        with:
-          script: |
-            const requested = JSON.parse(process.env.REQUESTED || "[]");
-            const { owner, repo } = context.repo;
-            const jobs = await github.paginate(
-              github.rest.actions.listJobsForWorkflowRun,
-              { owner, repo, run_id: context.runId, per_page: 100 }
-            );
-            const buildable = requested.filter((target) => {
-              const job = jobs.find(
-                (j) => j.name === `backport-apply-check (${target})`
-              );
-              return job && job.conclusion === "success";
-            });
-            core.info(`Buildable backport targets: 
${JSON.stringify(buildable)}`);
-            core.setOutput("targets", JSON.stringify(buildable));
-
-  backport:
-    needs: [precheck, backport-buildable]
-    if: ${{ needs.backport-buildable.outputs.targets != '' && 
needs.backport-buildable.outputs.targets != '[]' }}
-    strategy:
-      fail-fast: false
-      matrix:
-        target: ${{ fromJson(needs.backport-buildable.outputs.targets) }}
-    uses: ./.github/workflows/build.yml
-    with:
-      checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head
-      backport_target_branch: ${{ matrix.target }}
-      backport_commit_range: ${{ format('{0}..{1}', 
github.event.pull_request.base.sha, github.event.pull_request.head.sha) }}
-      job_name_suffix: ""
-      run_frontend: ${{ needs.precheck.outputs.run_frontend == 'true' }}
-      run_amber: ${{ needs.precheck.outputs.run_amber == 'true' }}
-      run_amber_integration: ${{ needs.precheck.outputs.run_amber_integration 
== 'true' }}
-      run_platform: ${{ needs.precheck.outputs.run_platform == 'true' }}
-      run_platform_integration: ${{ 
needs.precheck.outputs.run_platform_integration == 'true' }}
-      run_pyamber: ${{ needs.precheck.outputs.run_pyamber == 'true' }}
-      run_agent_service: ${{ needs.precheck.outputs.run_agent_service == 
'true' }}
-      run_infra: ${{ needs.precheck.outputs.run_infra == 'true' }}
-      run_pyright_language_service: ${{ 
needs.precheck.outputs.run_pyright_language_service == 'true' }}
-    secrets: inherit
-
   required-checks:
     # Do not rename this job — its display name is referenced in .asf.yaml.
     name: Required Checks
-    # The backport job is deliberately NOT a dependency here: it is an advisory
-    # signal, not a merge gate. A red pre-merge backport (a cherry-pick that
-    # conflicts, or a backported tree that fails to build) must not block the
-    # PR from landing on main — instead Direct Backport Push opens a draft
-    # backport PR for the author after merge. Keeping backport out of `needs`
-    # also lets this aggregator report as soon as build finishes.
     needs: [precheck, build]
     if: always()
     runs-on: ubuntu-latest

Reply via email to