From d9ed7718f447e19d01a0589c751845feb8ae39f5 Mon Sep 17 00:00:00 2001 From: Yicong-Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:59:30 -0700 Subject: [PATCH 01/27] ci: add release label backport workflows --- .github/scripts/prepare-backport-checkout.sh | 35 +++ .github/workflows/backport-ci.yml | 75 +++++ .github/workflows/create-backport-pr.yml | 139 +++++++++ .github/workflows/github-action-build.yml | 185 +----------- .github/workflows/reusable-build.yml | 300 +++++++++++++++++++ CONTRIBUTING.md | 2 + 6 files changed, 560 insertions(+), 176 deletions(-) create mode 100644 .github/scripts/prepare-backport-checkout.sh create mode 100644 .github/workflows/backport-ci.yml create mode 100644 .github/workflows/create-backport-pr.yml create mode 100644 .github/workflows/reusable-build.yml diff --git a/.github/scripts/prepare-backport-checkout.sh b/.github/scripts/prepare-backport-checkout.sh new file mode 100644 index 00000000000..d4bab1ad2f2 --- /dev/null +++ b/.github/scripts/prepare-backport-checkout.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# 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. + +set -euo pipefail + +target_branch="${1:?target branch is required}" +commit_range="${2:?commit range is required}" +workspace_branch="ci-backport-${target_branch//\//-}" + +git fetch --no-tags origin "${target_branch}" + +mapfile -t commits < <(git rev-list --reverse "${commit_range}") + +if [[ "${#commits[@]}" -eq 0 ]]; then + echo "No commits found in range ${commit_range}" >&2 + exit 1 +fi + +git checkout -B "${workspace_branch}" "origin/${target_branch}" +git cherry-pick -x "${commits[@]}" diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml new file mode 100644 index 00000000000..24ac5b962cd --- /dev/null +++ b/.github/workflows/backport-ci.yml @@ -0,0 +1,75 @@ +# 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. + +name: Backport Compatibility + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + detect-targets: + name: Detect release backport targets + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.targets.outputs.targets }} + has_targets: ${{ steps.targets.outputs.has_targets }} + steps: + - name: Collect release labels + id: targets + uses: actions/github-script@v8 + with: + script: | + const labels = context.payload.pull_request.labels.map((label) => label.name); + const targets = [...new Set(labels.filter((name) => /^release\/.+$/.test(name)))].sort(); + + core.info(`Backport targets: ${targets.join(", ") || "(none)"}`); + core.setOutput("targets", JSON.stringify(targets)); + core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); + + backport-ci: + name: Backport CI (${{ matrix.target }}) + needs: detect-targets + if: ${{ needs.detect-targets.outputs.has_targets == 'true' }} + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.detect-targets.outputs.targets) }} + uses: ./.github/workflows/reusable-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: ${{ format(' [backport {0}]', matrix.target) }} + summary_job_name: ${{ format('Backport CI ({0})', matrix.target) }} + run_frontend: true + run_scala: true + run_python: true + run_agent_service: true + secrets: inherit diff --git a/.github/workflows/create-backport-pr.yml b/.github/workflows/create-backport-pr.yml new file mode 100644 index 00000000000..de6738c7c08 --- /dev/null +++ b/.github/workflows/create-backport-pr.yml @@ -0,0 +1,139 @@ +# 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. + +name: Direct Backport Push + +on: + push: + branches: + - main + +permissions: + checks: read + contents: write + pull-requests: read + +jobs: + discover: + name: Discover direct backport targets + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.discover.outputs.pr_number }} + targets: ${{ steps.discover.outputs.targets }} + has_targets: ${{ steps.discover.outputs.has_targets }} + steps: + - name: Resolve merged PR and green targets + id: discover + uses: actions/github-script@v8 + with: + script: | + const sha = context.sha; + const { owner, repo } = context.repo; + + const response = await github.request( + "GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls", + { + owner, + repo, + commit_sha: sha, + } + ); + + const pullRequest = response.data.find((pr) => pr.merge_commit_sha === sha) ?? response.data[0]; + if (!pullRequest) { + core.info(`No merged pull request is associated with ${sha}.`); + core.setOutput("pr_number", ""); + core.setOutput("targets", "[]"); + core.setOutput("has_targets", "false"); + return; + } + + const requestedTargets = [...new Set( + pullRequest.labels + .map((label) => label.name) + .filter((name) => /^release\/.+$/.test(name)) + )].sort(); + + if (requestedTargets.length === 0) { + core.info(`PR #${pullRequest.number} does not request any backports.`); + core.setOutput("pr_number", String(pullRequest.number)); + core.setOutput("targets", "[]"); + core.setOutput("has_targets", "false"); + return; + } + + const checks = await github.request( + "GET /repos/{owner}/{repo}/commits/{ref}/check-runs", + { + owner, + repo, + ref: pullRequest.head.sha, + per_page: 100, + } + ); + + const greenTargets = requestedTargets.filter((target) => + checks.data.check_runs.some( + (run) => + run.name === `Backport CI (${target})` && + run.status === "completed" && + run.conclusion === "success" + ) + ); + + const skippedTargets = requestedTargets.filter((target) => !greenTargets.includes(target)); + if (skippedTargets.length > 0) { + core.warning(`Skipping targets without a successful Backport CI run: ${skippedTargets.join(", ")}`); + } + + core.setOutput("pr_number", String(pullRequest.number)); + core.setOutput("targets", JSON.stringify(greenTargets)); + core.setOutput("has_targets", greenTargets.length > 0 ? "true" : "false"); + + push-backports: + name: Push backport to ${{ matrix.target }} + needs: discover + if: ${{ needs.discover.outputs.has_targets == 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.discover.outputs.targets) }} + steps: + - name: Checkout main + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Cherry-pick merge commit onto target branch + env: + MERGE_SHA: ${{ github.sha }} + TARGET_BRANCH: ${{ matrix.target }} + run: | + set -euo pipefail + + parent_count=$(git rev-list --parents -n 1 "${MERGE_SHA}" | awk '{print NF-1}') + if [[ "${parent_count}" -ne 1 ]]; then + echo "Direct backport expects a squash-merged commit on main. ${MERGE_SHA} has ${parent_count} parents." >&2 + exit 1 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git fetch --no-tags origin "${TARGET_BRANCH}" + git checkout -B "${TARGET_BRANCH}" "origin/${TARGET_BRANCH}" + git cherry-pick -x "${MERGE_SHA}" + git push origin "HEAD:${TARGET_BRANCH}" diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index a3d840fc900..c93266e824e 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -17,9 +17,6 @@ name: Build -env: - NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} - on: push: branches: @@ -34,176 +31,12 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - frontend: - name: frontend (${{ matrix.os }}, 18) - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - include: - - os: macos-latest - arch: arm64 - - os: ubuntu-latest - arch: x64 - - os: windows-latest - arch: x64 - node-version: - - 20.19.0 - steps: - - name: Checkout Texera - uses: actions/checkout@v5 - - name: Setup node - uses: actions/setup-node@v5 - with: - node-version: ${{ matrix.node-version }} - architecture: ${{ matrix.arch }} - - uses: actions/cache@v4 - with: - path: frontend/.yarn/cache - key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.node-version }}-yarn-cache-v4-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.node-version }}-yarn-cache-v4- - - name: Prepare Yarn 4.14.1 - run: corepack enable && corepack prepare yarn@4.14.1 --activate - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - name: Install dependency - timeout-minutes: 20 - run: yarn --cwd frontend install --immutable --inline-builds --network-timeout=100000 - - name: Lint with Prettier & ESLint - run: yarn --cwd frontend format:ci - - name: Run frontend unit tests - run: yarn --cwd frontend run test:ci - - name: Prod build - run: yarn --cwd frontend run build:ci - - scala: - strategy: - matrix: - os: [ ubuntu-22.04 ] - java-version: [ 11 ] - runs-on: ${{ matrix.os }} - env: - JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 - JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 - - services: - postgres: - image: postgres - env: - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - # Add a health check so steps wait until Postgres is ready - options: >- - --health-cmd="pg_isready -U postgres" - --health-interval=10s - --health-timeout=5s - --health-retries=5 - steps: - - name: Checkout - uses: actions/checkout@v5 - - name: Setup JDK - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: 11 - - name: Setup Python for Scala tests - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - name: Show Python - run: python --version || python3 --version - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f amber/requirements.txt ]; then pip install -r amber/requirements.txt; fi - if [ -f amber/operator-requirements.txt ]; then pip install -r amber/operator-requirements.txt; fi - - name: Setup sbt launcher - uses: sbt/setup-sbt@508b753e53cb6095967669e0911487d2b9bc9f41 # v1.1.22 - - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 - with: - extraSbtFiles: '["*.sbt", "project/**.{scala,sbt}", "project/build.properties" ]' - - name: Lint with scalafmt - run: sbt scalafmtCheckAll - - name: Create Databases - run: | - psql -h localhost -U postgres -f sql/texera_ddl.sql - psql -h localhost -U postgres -f sql/iceberg_postgres_catalog.sql - psql -h localhost -U postgres -f sql/texera_lakefs.sql - env: - PGPASSWORD: postgres - - name: Create texera_db_for_test_cases - run: psql -h localhost -U postgres -v DB_NAME=texera_db_for_test_cases -f sql/texera_ddl.sql - env: - PGPASSWORD: postgres - - name: Compile with sbt - run: sbt clean package - - name: Lint with scalafix - run: sbt "scalafixAll --check" - - name: Set docker-java API version - run: | - echo "api.version=1.52" >> ~/.docker-java.properties - cat ~/.docker-java.properties - - name: Run backend tests - run: sbt test - - python: - strategy: - matrix: - os: [ ubuntu-latest ] - python-version: [ '3.10', '3.11', '3.12', '3.13' ] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Texera - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f amber/requirements.txt ]; then pip install -r amber/requirements.txt; fi - if [ -f amber/operator-requirements.txt ]; then pip install -r amber/operator-requirements.txt; fi - - name: Install PostgreSQL - run: sudo apt-get update && sudo apt-get install -y postgresql - - name: Start PostgreSQL Service - run: sudo systemctl start postgresql - - name: Create Database and User - run: | - cd sql && sudo -u postgres psql -f iceberg_postgres_catalog.sql - - name: Lint with Ruff - run: | - cd amber/src/main/python && ruff check . && ruff format --check . - - name: Test with pytest - run: | - cd amber/src/main/python && pytest -sv - - agent-service: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - bun-version: ['1.3.3'] - defaults: - run: - working-directory: agent-service - steps: - - name: Checkout Texera - uses: actions/checkout@v5 - - name: Setup Bun - run: | - curl -fsSL https://bun.sh/install | bash -s -- bun-v${{ matrix.bun-version }} - echo "$HOME/.bun/bin" >> $GITHUB_PATH - - name: Install dependencies - run: bun install --frozen-lockfile - - name: Lint with Prettier - run: bun run format:check - - name: Typecheck - run: bun run typecheck - - name: Run unit tests - run: bun test + build: + uses: ./.github/workflows/reusable-build.yml + with: + summary_job_name: Build Summary + run_frontend: true + run_scala: true + run_python: true + run_agent_service: true + secrets: inherit diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml new file mode 100644 index 00000000000..72680e633a4 --- /dev/null +++ b/.github/workflows/reusable-build.yml @@ -0,0 +1,300 @@ +# 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. + +name: Reusable Build + +on: + workflow_call: + inputs: + checkout_ref: + required: false + type: string + default: "" + backport_target_branch: + required: false + type: string + default: "" + backport_commit_range: + required: false + type: string + default: "" + job_name_suffix: + required: false + type: string + default: "" + run_frontend: + required: false + type: boolean + default: true + run_scala: + required: false + type: boolean + default: true + run_python: + required: false + type: boolean + default: true + run_agent_service: + required: false + type: boolean + default: true + summary_job_name: + required: false + type: string + default: "Build Summary" + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +jobs: + frontend: + if: ${{ inputs.run_frontend }} + name: ${{ format('frontend{0} ({1}, 18)', inputs.job_name_suffix, matrix.os) }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: macos-latest + arch: arm64 + - os: ubuntu-latest + arch: x64 + - os: windows-latest + arch: x64 + node-version: + - 20.19.0 + steps: + - name: Checkout Texera + uses: actions/checkout@v5 + with: + ref: ${{ inputs.checkout_ref || github.sha }} + fetch-depth: 0 + - name: Prepare backport workspace + if: ${{ inputs.backport_target_branch != '' }} + run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + - name: Setup node + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ matrix.arch }} + - uses: actions/cache@v4 + with: + path: frontend/.yarn/cache + key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.node-version }}-yarn-cache-v4-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.node-version }}-yarn-cache-v4- + - name: Prepare Yarn 4.14.1 + run: corepack enable && corepack prepare yarn@4.14.1 --activate + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Install dependency + timeout-minutes: 20 + run: yarn --cwd frontend install --immutable --inline-builds --network-timeout=100000 + - name: Lint with Prettier & ESLint + run: yarn --cwd frontend format:ci + - name: Run frontend unit tests + run: yarn --cwd frontend run test:ci + - name: Prod build + run: yarn --cwd frontend run build:ci + + scala: + if: ${{ inputs.run_scala }} + name: ${{ format('scala{0} ({1}, 11)', inputs.job_name_suffix, matrix.os) }} + strategy: + matrix: + os: [ubuntu-22.04] + java-version: [11] + runs-on: ${{ matrix.os }} + env: + JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U postgres" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.checkout_ref || github.sha }} + fetch-depth: 0 + - name: Prepare backport workspace + if: ${{ inputs.backport_target_branch != '' }} + run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + - name: Setup JDK + uses: actions/setup-java@v5 + with: + distribution: "temurin" + java-version: 11 + - name: Setup Python for Scala tests + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Show Python + run: python --version || python3 --version + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f amber/requirements.txt ]; then pip install -r amber/requirements.txt; fi + if [ -f amber/operator-requirements.txt ]; then pip install -r amber/operator-requirements.txt; fi + - name: Setup sbt launcher + uses: sbt/setup-sbt@508b753e53cb6095967669e0911487d2b9bc9f41 # v1.1.22 + - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + with: + extraSbtFiles: '["*.sbt", "project/**.{scala,sbt}", "project/build.properties" ]' + - name: Lint with scalafmt + run: sbt scalafmtCheckAll + - name: Create Databases + run: | + psql -h localhost -U postgres -f sql/texera_ddl.sql + psql -h localhost -U postgres -f sql/iceberg_postgres_catalog.sql + psql -h localhost -U postgres -f sql/texera_lakefs.sql + env: + PGPASSWORD: postgres + - name: Create texera_db_for_test_cases + run: psql -h localhost -U postgres -v DB_NAME=texera_db_for_test_cases -f sql/texera_ddl.sql + env: + PGPASSWORD: postgres + - name: Compile with sbt + run: sbt clean package + - name: Lint with scalafix + run: sbt "scalafixAll --check" + - name: Set docker-java API version + run: | + echo "api.version=1.52" >> ~/.docker-java.properties + cat ~/.docker-java.properties + - name: Run backend tests + run: sbt test + + python: + if: ${{ inputs.run_python }} + name: ${{ format('python{0} ({1}, {2})', inputs.job_name_suffix, matrix.os, matrix.python-version) }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.10", "3.11", "3.12", "3.13"] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Texera + uses: actions/checkout@v5 + with: + ref: ${{ inputs.checkout_ref || github.sha }} + fetch-depth: 0 + - name: Prepare backport workspace + if: ${{ inputs.backport_target_branch != '' }} + run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f amber/requirements.txt ]; then pip install -r amber/requirements.txt; fi + if [ -f amber/operator-requirements.txt ]; then pip install -r amber/operator-requirements.txt; fi + - name: Install PostgreSQL + run: sudo apt-get update && sudo apt-get install -y postgresql + - name: Start PostgreSQL Service + run: sudo systemctl start postgresql + - name: Create Database and User + run: | + cd sql && sudo -u postgres psql -f iceberg_postgres_catalog.sql + - name: Lint with Ruff + run: | + cd amber/src/main/python && ruff check . && ruff format --check . + - name: Test with pytest + run: | + cd amber/src/main/python && pytest -sv + + agent-service: + if: ${{ inputs.run_agent_service }} + name: ${{ format('agent-service{0} ({1})', inputs.job_name_suffix, matrix.os) }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + bun-version: ["1.3.3"] + defaults: + run: + working-directory: agent-service + steps: + - name: Checkout Texera + uses: actions/checkout@v5 + with: + ref: ${{ inputs.checkout_ref || github.sha }} + fetch-depth: 0 + - name: Prepare backport workspace + if: ${{ inputs.backport_target_branch != '' }} + run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + - name: Setup Bun + run: | + curl -fsSL https://bun.sh/install | bash -s -- bun-v${{ matrix.bun-version }} + echo "$HOME/.bun/bin" >> $GITHUB_PATH + - name: Install dependencies + run: bun install --frozen-lockfile + - name: Lint with Prettier + run: bun run format:check + - name: Typecheck + run: bun run typecheck + - name: Run unit tests + run: bun test + + summary: + name: ${{ inputs.summary_job_name }} + if: ${{ always() }} + needs: + - frontend + - scala + - python + - agent-service + runs-on: ubuntu-latest + steps: + - name: Fail if any CI job failed + env: + FRONTEND_RESULT: ${{ needs.frontend.result }} + SCALA_RESULT: ${{ needs.scala.result }} + PYTHON_RESULT: ${{ needs.python.result }} + AGENT_SERVICE_RESULT: ${{ needs.agent-service.result }} + RUN_FRONTEND: ${{ inputs.run_frontend }} + RUN_SCALA: ${{ inputs.run_scala }} + RUN_PYTHON: ${{ inputs.run_python }} + RUN_AGENT_SERVICE: ${{ inputs.run_agent_service }} + run: | + check_result() { + local enabled="$1" + local result="$2" + if [[ "$enabled" == "true" && "$result" != "success" ]]; then + echo "A required CI job failed: $result" + exit 1 + fi + } + + check_result "$RUN_FRONTEND" "$FRONTEND_RESULT" + check_result "$RUN_SCALA" "$SCALA_RESULT" + check_result "$RUN_PYTHON" "$PYTHON_RESULT" + check_result "$RUN_AGENT_SERVICE" "$AGENT_SERVICE_RESULT" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3b0dfdd5e8..b703f5ba48b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,6 +84,8 @@ yarn format:fix ### 4. PR Review - [ ] Ask a Texera Committer (by commenting on the PR) to triage your PR, i.e., request a reviewer, and assign the PR to you. - [ ] Add appropriate labels such as `fix`, `enhancement`, `docs`, etc. +- [ ] If the change should also land in a release branch, add the release branch label directly, such as `release/1.1`. +- [ ] When a `release/*` label is present, GitHub Actions will run the PR changes against that target release branch and, after the PR is squash-merged into `main`, automatically push the same change to the labeled release branch if the backport CI passed. - [ ] Ensure that all CI checks pass (see [GitHub Actions](https://github.com/Texera/texera/actions)). - [ ] Fully test your changes locally. From 5568cc2970a6b8b496420a92f2ea85f8fd71a584 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:50:22 -0700 Subject: [PATCH 02/27] ci: harden backport workflow portability --- .github/scripts/prepare-backport-checkout.sh | 7 ++++++- .github/workflows/reusable-build.yml | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/scripts/prepare-backport-checkout.sh b/.github/scripts/prepare-backport-checkout.sh index d4bab1ad2f2..a4d5391066f 100644 --- a/.github/scripts/prepare-backport-checkout.sh +++ b/.github/scripts/prepare-backport-checkout.sh @@ -23,8 +23,13 @@ commit_range="${2:?commit range is required}" workspace_branch="ci-backport-${target_branch//\//-}" git fetch --no-tags origin "${target_branch}" +git config user.name "github-actions[bot]" +git config user.email "41898282+github-actions[bot]@users.noreply.github.com" -mapfile -t commits < <(git rev-list --reverse "${commit_range}") +commits=() +while IFS= read -r commit; do + commits+=("${commit}") +done < <(git rev-list --reverse "${commit_range}") if [[ "${#commits[@]}" -eq 0 ]]; then echo "No commits found in range ${commit_range}" >&2 diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 72680e633a4..3f11b295cc1 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -85,7 +85,8 @@ jobs: fetch-depth: 0 - name: Prepare backport workspace if: ${{ inputs.backport_target_branch != '' }} - run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + working-directory: ${{ github.workspace }} + run: bash ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" - name: Setup node uses: actions/setup-node@v5 with: @@ -144,7 +145,8 @@ jobs: fetch-depth: 0 - name: Prepare backport workspace if: ${{ inputs.backport_target_branch != '' }} - run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + working-directory: ${{ github.workspace }} + run: bash ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" - name: Setup JDK uses: actions/setup-java@v5 with: @@ -206,7 +208,7 @@ jobs: fetch-depth: 0 - name: Prepare backport workspace if: ${{ inputs.backport_target_branch != '' }} - run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + run: bash ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: @@ -250,7 +252,8 @@ jobs: fetch-depth: 0 - name: Prepare backport workspace if: ${{ inputs.backport_target_branch != '' }} - run: ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" + working-directory: ${{ github.workspace }} + run: bash ./.github/scripts/prepare-backport-checkout.sh "${{ inputs.backport_target_branch }}" "${{ inputs.backport_commit_range }}" - name: Setup Bun run: | curl -fsSL https://bun.sh/install | bash -s -- bun-v${{ matrix.bun-version }} From 234f7e9c4cd48912d73cff9bf20f2f3d9f1b0058 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:55:31 -0700 Subject: [PATCH 03/27] ci: preserve existing build check names --- .github/workflows/github-action-build.yml | 183 ++++++++++++++++++++-- 1 file changed, 174 insertions(+), 9 deletions(-) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index c93266e824e..3d373313bd6 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -17,6 +17,9 @@ name: Build +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + on: push: branches: @@ -31,12 +34,174 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - build: - uses: ./.github/workflows/reusable-build.yml - with: - summary_job_name: Build Summary - run_frontend: true - run_scala: true - run_python: true - run_agent_service: true - secrets: inherit + frontend: + name: frontend (${{ matrix.os }}, 18) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: macos-latest + arch: arm64 + - os: ubuntu-latest + arch: x64 + - os: windows-latest + arch: x64 + node-version: + - 20.19.0 + steps: + - name: Checkout Texera + uses: actions/checkout@v5 + - name: Setup node + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ matrix.arch }} + - uses: actions/cache@v4 + with: + path: frontend/.yarn/cache + key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.node-version }}-yarn-cache-v4-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.node-version }}-yarn-cache-v4- + - name: Prepare Yarn 4.14.1 + run: corepack enable && corepack prepare yarn@4.14.1 --activate + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + - name: Install dependency + timeout-minutes: 20 + run: yarn --cwd frontend install --immutable --inline-builds --network-timeout=100000 + - name: Lint with Prettier & ESLint + run: yarn --cwd frontend format:ci + - name: Run frontend unit tests + run: yarn --cwd frontend run test:ci + - name: Prod build + run: yarn --cwd frontend run build:ci + + scala: + strategy: + matrix: + os: [ubuntu-22.04] + java-version: [11] + runs-on: ${{ matrix.os }} + env: + JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U postgres" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Setup JDK + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 11 + - name: Setup Python for Scala tests + uses: actions/setup-python@v6 + with: + python-version: '3.11' + - name: Show Python + run: python --version || python3 --version + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f amber/requirements.txt ]; then pip install -r amber/requirements.txt; fi + if [ -f amber/operator-requirements.txt ]; then pip install -r amber/operator-requirements.txt; fi + - name: Setup sbt launcher + uses: sbt/setup-sbt@508b753e53cb6095967669e0911487d2b9bc9f41 # v1.1.22 + - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + with: + extraSbtFiles: '["*.sbt", "project/**.{scala,sbt}", "project/build.properties" ]' + - name: Lint with scalafmt + run: sbt scalafmtCheckAll + - name: Create Databases + run: | + psql -h localhost -U postgres -f sql/texera_ddl.sql + psql -h localhost -U postgres -f sql/iceberg_postgres_catalog.sql + psql -h localhost -U postgres -f sql/texera_lakefs.sql + env: + PGPASSWORD: postgres + - name: Create texera_db_for_test_cases + run: psql -h localhost -U postgres -v DB_NAME=texera_db_for_test_cases -f sql/texera_ddl.sql + env: + PGPASSWORD: postgres + - name: Compile with sbt + run: sbt clean package + - name: Lint with scalafix + run: sbt "scalafixAll --check" + - name: Set docker-java API version + run: | + echo "api.version=1.52" >> ~/.docker-java.properties + cat ~/.docker-java.properties + - name: Run backend tests + run: sbt test + + python: + strategy: + matrix: + os: [ubuntu-latest] + python-version: ['3.10', '3.11', '3.12', '3.13'] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Texera + uses: actions/checkout@v5 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f amber/requirements.txt ]; then pip install -r amber/requirements.txt; fi + if [ -f amber/operator-requirements.txt ]; then pip install -r amber/operator-requirements.txt; fi + - name: Install PostgreSQL + run: sudo apt-get update && sudo apt-get install -y postgresql + - name: Start PostgreSQL Service + run: sudo systemctl start postgresql + - name: Create Database and User + run: | + cd sql && sudo -u postgres psql -f iceberg_postgres_catalog.sql + - name: Lint with Ruff + run: | + cd amber/src/main/python && ruff check . && ruff format --check . + - name: Test with pytest + run: | + cd amber/src/main/python && pytest -sv + + agent-service: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + bun-version: ['1.3.3'] + defaults: + run: + working-directory: agent-service + steps: + - name: Checkout Texera + uses: actions/checkout@v5 + - name: Setup Bun + run: | + curl -fsSL https://bun.sh/install | bash -s -- bun-v${{ matrix.bun-version }} + echo "$HOME/.bun/bin" >> $GITHUB_PATH + - name: Install dependencies + run: bun install --frozen-lockfile + - name: Lint with Prettier + run: bun run format:check + - name: Typecheck + run: bun run typecheck + - name: Run unit tests + run: bun test From 4b647026e7e67e1bcc784896bad4b50bb6b9736c Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 00:14:24 -0700 Subject: [PATCH 04/27] ci: dispatch backport CI from build workflow Fold the release-label detection and per-target matrix dispatch into the main Build workflow so backport CI starts as part of every PR build. Existing inline build jobs skip on label-only events to avoid redundant runs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 75 ----------------------- .github/workflows/github-action-build.yml | 51 +++++++++++++++ 2 files changed, 51 insertions(+), 75 deletions(-) delete mode 100644 .github/workflows/backport-ci.yml diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml deleted file mode 100644 index 24ac5b962cd..00000000000 --- a/.github/workflows/backport-ci.yml +++ /dev/null @@ -1,75 +0,0 @@ -# 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. - -name: Backport Compatibility - -on: - pull_request: - types: - - opened - - reopened - - synchronize - - labeled - - unlabeled - -permissions: - contents: read - pull-requests: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - detect-targets: - name: Detect release backport targets - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.targets.outputs.targets }} - has_targets: ${{ steps.targets.outputs.has_targets }} - steps: - - name: Collect release labels - id: targets - uses: actions/github-script@v8 - with: - script: | - const labels = context.payload.pull_request.labels.map((label) => label.name); - const targets = [...new Set(labels.filter((name) => /^release\/.+$/.test(name)))].sort(); - - core.info(`Backport targets: ${targets.join(", ") || "(none)"}`); - core.setOutput("targets", JSON.stringify(targets)); - core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); - - backport-ci: - name: Backport CI (${{ matrix.target }}) - needs: detect-targets - if: ${{ needs.detect-targets.outputs.has_targets == 'true' }} - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.detect-targets.outputs.targets) }} - uses: ./.github/workflows/reusable-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: ${{ format(' [backport {0}]', matrix.target) }} - summary_job_name: ${{ format('Backport CI ({0})', matrix.target) }} - run_frontend: true - run_scala: true - run_python: true - run_agent_service: true - secrets: inherit diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 3d373313bd6..e00f8fcfe77 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -27,6 +27,12 @@ on: - 'main' - 'release/**' pull_request: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled workflow_dispatch: concurrency: @@ -34,7 +40,49 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: + detect-backport-targets: + name: Detect release backport targets + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.targets.outputs.targets }} + has_targets: ${{ steps.targets.outputs.has_targets }} + steps: + - name: Collect release labels + id: targets + uses: actions/github-script@v8 + with: + script: | + const labels = context.payload.pull_request.labels.map((label) => label.name); + const targets = [...new Set(labels.filter((name) => /^release\/.+$/.test(name)))].sort(); + + core.info(`Backport targets: ${targets.join(", ") || "(none)"}`); + core.setOutput("targets", JSON.stringify(targets)); + core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); + + backport-ci: + name: Backport CI (${{ matrix.target }}) + needs: detect-backport-targets + if: ${{ needs.detect-backport-targets.outputs.has_targets == 'true' }} + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.detect-backport-targets.outputs.targets) }} + uses: ./.github/workflows/reusable-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: ${{ format(' [backport {0}]', matrix.target) }} + summary_job_name: ${{ format('Backport CI ({0})', matrix.target) }} + run_frontend: true + run_scala: true + run_python: true + run_agent_service: true + secrets: inherit + frontend: + if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} name: frontend (${{ matrix.os }}, 18) runs-on: ${{ matrix.os }} strategy: @@ -80,6 +128,7 @@ jobs: run: yarn --cwd frontend run build:ci scala: + if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} strategy: matrix: os: [ubuntu-22.04] @@ -149,6 +198,7 @@ jobs: run: sbt test python: + if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} strategy: matrix: os: [ubuntu-latest] @@ -181,6 +231,7 @@ jobs: cd amber/src/main/python && pytest -sv agent-service: + if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From f81edc83b674f53c0bbedc02122881c44f15cfc7 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 00:21:34 -0700 Subject: [PATCH 05/27] ci: route build vs backport via precheck job Add a precheck routing job that inspects the PR event and labels: - non-PR events (push, dispatch) run the main build - PR labeled with a release/* label runs only that backport target - PR unlabel events run nothing extra - other PR events (opened, reopened, synchronize) run only the main build Main build jobs and the backport matrix both gate on precheck outputs, so each event triggers exactly one route. Drop the static name on the backport-ci job so skipped runs no longer surface an unevaluated matrix placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 62 +++++++++++++++-------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index e00f8fcfe77..ed1eab82bf4 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -40,34 +40,52 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - detect-backport-targets: - name: Detect release backport targets - if: ${{ github.event_name == 'pull_request' }} + precheck: + name: Pre-flight routing runs-on: ubuntu-latest outputs: - targets: ${{ steps.targets.outputs.targets }} - has_targets: ${{ steps.targets.outputs.has_targets }} + should_run_main_build: ${{ steps.route.outputs.should_run_main_build }} + backport_targets: ${{ steps.route.outputs.backport_targets }} + has_backport_targets: ${{ steps.route.outputs.has_backport_targets }} steps: - - name: Collect release labels - id: targets + - name: Decide routing + id: route uses: actions/github-script@v8 with: script: | - const labels = context.payload.pull_request.labels.map((label) => label.name); - const targets = [...new Set(labels.filter((name) => /^release\/.+$/.test(name)))].sort(); + const eventName = context.eventName; + const action = context.payload.action || ""; + let shouldRunMainBuild = false; + let backportTargets = []; - core.info(`Backport targets: ${targets.join(", ") || "(none)"}`); - core.setOutput("targets", JSON.stringify(targets)); - core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); + if (eventName !== "pull_request") { + shouldRunMainBuild = true; + } else if (action === "labeled") { + const newLabel = context.payload.label && context.payload.label.name; + if (newLabel && /^release\/.+$/.test(newLabel)) { + backportTargets = [newLabel]; + } + } else if (action === "unlabeled") { + // Removing a label cancels nothing and triggers no extra build. + } else { + shouldRunMainBuild = true; + } + + core.info(`event=${eventName} action=${action || "(none)"}`); + core.info(`should_run_main_build=${shouldRunMainBuild}`); + core.info(`backport_targets=${JSON.stringify(backportTargets)}`); + + core.setOutput("should_run_main_build", shouldRunMainBuild ? "true" : "false"); + core.setOutput("backport_targets", JSON.stringify(backportTargets)); + core.setOutput("has_backport_targets", backportTargets.length > 0 ? "true" : "false"); backport-ci: - name: Backport CI (${{ matrix.target }}) - needs: detect-backport-targets - if: ${{ needs.detect-backport-targets.outputs.has_targets == 'true' }} + needs: precheck + if: ${{ needs.precheck.outputs.has_backport_targets == 'true' }} strategy: fail-fast: false matrix: - target: ${{ fromJson(needs.detect-backport-targets.outputs.targets) }} + target: ${{ fromJson(needs.precheck.outputs.backport_targets) }} uses: ./.github/workflows/reusable-build.yml with: checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head @@ -82,7 +100,8 @@ jobs: secrets: inherit frontend: - if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} + needs: precheck + if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} name: frontend (${{ matrix.os }}, 18) runs-on: ${{ matrix.os }} strategy: @@ -128,7 +147,8 @@ jobs: run: yarn --cwd frontend run build:ci scala: - if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} + needs: precheck + if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} strategy: matrix: os: [ubuntu-22.04] @@ -198,7 +218,8 @@ jobs: run: sbt test python: - if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} + needs: precheck + if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} strategy: matrix: os: [ubuntu-latest] @@ -231,7 +252,8 @@ jobs: cd amber/src/main/python && pytest -sv agent-service: - if: ${{ github.event_name != 'pull_request' || (github.event.action != 'labeled' && github.event.action != 'unlabeled') }} + needs: precheck + if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From 2175cae69e71f5e13433e1019ae574bc22aa63cf Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 00:31:28 -0700 Subject: [PATCH 06/27] ci: split backport CI into standalone workflow Move release-label backport CI into its own workflow file triggered only by pull_request labeled events. The build workflow no longer references backport jobs, so regular PR runs no longer surface a skipped backport-ci entry. Backport CI runs only when the added label matches release/*; other labels trigger a workflow run whose single job is filtered out by its if-guard. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 47 +++++++++++++++ .github/workflows/github-action-build.yml | 73 ----------------------- 2 files changed, 47 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/backport-ci.yml diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml new file mode 100644 index 00000000000..22932d198fb --- /dev/null +++ b/.github/workflows/backport-ci.yml @@ -0,0 +1,47 @@ +# 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. + +name: Backport Compatibility + +on: + pull_request: + types: + - labeled + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }}-${{ github.event.label.name }} + cancel-in-progress: true + +jobs: + backport-ci: + if: ${{ startsWith(github.event.label.name, 'release/') }} + name: Backport CI (${{ github.event.label.name }}) + uses: ./.github/workflows/reusable-build.yml + with: + checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head + backport_target_branch: ${{ github.event.label.name }} + backport_commit_range: ${{ format('{0}..{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) }} + job_name_suffix: ${{ format(' [backport {0}]', github.event.label.name) }} + summary_job_name: ${{ format('Backport CI ({0})', github.event.label.name) }} + run_frontend: true + run_scala: true + run_python: true + run_agent_service: true + secrets: inherit diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index ed1eab82bf4..3d373313bd6 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -27,12 +27,6 @@ on: - 'main' - 'release/**' pull_request: - types: - - opened - - reopened - - synchronize - - labeled - - unlabeled workflow_dispatch: concurrency: @@ -40,68 +34,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - precheck: - name: Pre-flight routing - runs-on: ubuntu-latest - outputs: - should_run_main_build: ${{ steps.route.outputs.should_run_main_build }} - backport_targets: ${{ steps.route.outputs.backport_targets }} - has_backport_targets: ${{ steps.route.outputs.has_backport_targets }} - steps: - - name: Decide routing - id: route - uses: actions/github-script@v8 - with: - script: | - const eventName = context.eventName; - const action = context.payload.action || ""; - let shouldRunMainBuild = false; - let backportTargets = []; - - if (eventName !== "pull_request") { - shouldRunMainBuild = true; - } else if (action === "labeled") { - const newLabel = context.payload.label && context.payload.label.name; - if (newLabel && /^release\/.+$/.test(newLabel)) { - backportTargets = [newLabel]; - } - } else if (action === "unlabeled") { - // Removing a label cancels nothing and triggers no extra build. - } else { - shouldRunMainBuild = true; - } - - core.info(`event=${eventName} action=${action || "(none)"}`); - core.info(`should_run_main_build=${shouldRunMainBuild}`); - core.info(`backport_targets=${JSON.stringify(backportTargets)}`); - - core.setOutput("should_run_main_build", shouldRunMainBuild ? "true" : "false"); - core.setOutput("backport_targets", JSON.stringify(backportTargets)); - core.setOutput("has_backport_targets", backportTargets.length > 0 ? "true" : "false"); - - backport-ci: - needs: precheck - if: ${{ needs.precheck.outputs.has_backport_targets == 'true' }} - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.precheck.outputs.backport_targets) }} - uses: ./.github/workflows/reusable-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: ${{ format(' [backport {0}]', matrix.target) }} - summary_job_name: ${{ format('Backport CI ({0})', matrix.target) }} - run_frontend: true - run_scala: true - run_python: true - run_agent_service: true - secrets: inherit - frontend: - needs: precheck - if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} name: frontend (${{ matrix.os }}, 18) runs-on: ${{ matrix.os }} strategy: @@ -147,8 +80,6 @@ jobs: run: yarn --cwd frontend run build:ci scala: - needs: precheck - if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} strategy: matrix: os: [ubuntu-22.04] @@ -218,8 +149,6 @@ jobs: run: sbt test python: - needs: precheck - if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} strategy: matrix: os: [ubuntu-latest] @@ -252,8 +181,6 @@ jobs: cd amber/src/main/python && pytest -sv agent-service: - needs: precheck - if: ${{ needs.precheck.outputs.should_run_main_build == 'true' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From a70587053241d29fae48af7c71dcfef17c09e16d Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 00:51:47 -0700 Subject: [PATCH 07/27] ci: shorten backport check names Trim the workflow name to "Backport", drop the redundant outer job display name, and pass an empty job_name_suffix so reusable-build job names no longer repeat the target label. The summary check remains "Backport CI ()" so create-backport-pr.yml lookups still resolve, and run-name keeps the target visible on the Actions tab. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml index 22932d198fb..1022301dd07 100644 --- a/.github/workflows/backport-ci.yml +++ b/.github/workflows/backport-ci.yml @@ -14,7 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -name: Backport Compatibility +name: Backport +run-name: Backport ${{ github.event.label.name }} (PR #${{ github.event.pull_request.number }}) on: pull_request: @@ -30,15 +31,14 @@ concurrency: cancel-in-progress: true jobs: - backport-ci: + run: if: ${{ startsWith(github.event.label.name, 'release/') }} - name: Backport CI (${{ github.event.label.name }}) uses: ./.github/workflows/reusable-build.yml with: checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head backport_target_branch: ${{ github.event.label.name }} backport_commit_range: ${{ format('{0}..{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) }} - job_name_suffix: ${{ format(' [backport {0}]', github.event.label.name) }} + job_name_suffix: "" summary_job_name: ${{ format('Backport CI ({0})', github.event.label.name) }} run_frontend: true run_scala: true From 91aab8c26d838366e29e58c994950de09f45a5a0 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 00:58:16 -0700 Subject: [PATCH 08/27] ci: shorten backport check display names Drop CI from the summary check display (Backport CI -> Backport) and override the wrapper job's display so the path no longer surfaces the 'run' job key. Update create-backport-pr.yml's lookup string to match the new display name; the YAML job key, file name, and post-merge references that need 'CI' or 'run' are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 3 ++- .github/workflows/create-backport-pr.yml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml index 1022301dd07..82443f2341f 100644 --- a/.github/workflows/backport-ci.yml +++ b/.github/workflows/backport-ci.yml @@ -33,13 +33,14 @@ concurrency: jobs: run: if: ${{ startsWith(github.event.label.name, 'release/') }} + name: ${{ github.event.label.name }} uses: ./.github/workflows/reusable-build.yml with: checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head backport_target_branch: ${{ github.event.label.name }} backport_commit_range: ${{ format('{0}..{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) }} job_name_suffix: "" - summary_job_name: ${{ format('Backport CI ({0})', github.event.label.name) }} + summary_job_name: ${{ format('Backport ({0})', github.event.label.name) }} run_frontend: true run_scala: true run_python: true diff --git a/.github/workflows/create-backport-pr.yml b/.github/workflows/create-backport-pr.yml index de6738c7c08..01ca3f9a9da 100644 --- a/.github/workflows/create-backport-pr.yml +++ b/.github/workflows/create-backport-pr.yml @@ -88,7 +88,7 @@ jobs: const greenTargets = requestedTargets.filter((target) => checks.data.check_runs.some( (run) => - run.name === `Backport CI (${target})` && + run.name === `Backport (${target})` && run.status === "completed" && run.conclusion === "success" ) @@ -96,7 +96,7 @@ jobs: const skippedTargets = requestedTargets.filter((target) => !greenTargets.includes(target)); if (skippedTargets.length > 0) { - core.warning(`Skipping targets without a successful Backport CI run: ${skippedTargets.join(", ")}`); + core.warning(`Skipping targets without a successful Backport run: ${skippedTargets.join(", ")}`); } core.setOutput("pr_number", String(pullRequest.number)); From 565e67ba3e879e99cfee1a71c932f16a7b1a5b1f Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:01:55 -0700 Subject: [PATCH 09/27] ci: identify backport runs by workflow run, not check name Drop the target from the summary check name so the backport CI path becomes 'Backport / / Build Summary' with the target shown exactly once. Replace the check_runs lookup in create-backport-pr.yml with a workflow-runs query filtered by head_sha and matched on the backport run's display_title (the run-name we set per target). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 1 - .github/workflows/create-backport-pr.yml | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml index 82443f2341f..a699eacb93c 100644 --- a/.github/workflows/backport-ci.yml +++ b/.github/workflows/backport-ci.yml @@ -40,7 +40,6 @@ jobs: backport_target_branch: ${{ github.event.label.name }} backport_commit_range: ${{ format('{0}..{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) }} job_name_suffix: "" - summary_job_name: ${{ format('Backport ({0})', github.event.label.name) }} run_frontend: true run_scala: true run_python: true diff --git a/.github/workflows/create-backport-pr.yml b/.github/workflows/create-backport-pr.yml index 01ca3f9a9da..f77a116b2fd 100644 --- a/.github/workflows/create-backport-pr.yml +++ b/.github/workflows/create-backport-pr.yml @@ -22,7 +22,7 @@ on: - main permissions: - checks: read + actions: read contents: write pull-requests: read @@ -75,24 +75,26 @@ jobs: return; } - const checks = await github.request( - "GET /repos/{owner}/{repo}/commits/{ref}/check-runs", + const backportRuns = await github.paginate( + github.rest.actions.listWorkflowRuns, { owner, repo, - ref: pullRequest.head.sha, + workflow_id: "backport-ci.yml", + head_sha: pullRequest.head.sha, per_page: 100, } ); - const greenTargets = requestedTargets.filter((target) => - checks.data.check_runs.some( + const greenTargets = requestedTargets.filter((target) => { + const expectedTitle = `Backport ${target} (PR #${pullRequest.number})`; + return backportRuns.some( (run) => - run.name === `Backport (${target})` && + run.display_title === expectedTitle && run.status === "completed" && run.conclusion === "success" - ) - ); + ); + }); const skippedTargets = requestedTargets.filter((target) => !greenTargets.includes(target)); if (skippedTargets.length > 0) { From 06e2b650a0f01ff20d2eaf3ce34749f4fc0552fe Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:10:58 -0700 Subject: [PATCH 10/27] ci: re-run backport CI on PR sync events Trigger Backport CI on opened, reopened, synchronize, and labeled PR events. A detect job picks the just-added label on labeled events and falls back to all current release/* labels for code-changing events, then a matrix run job validates each target. Switch create-backport-pr.yml's lookup to listJobsForWorkflowRun and match per-target jobs by their composite name " / Build Summary"; the previous per-run display_title match no longer works once a single run can cover multiple targets. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 47 +++++++++++++++++++++--- .github/workflows/create-backport-pr.yml | 33 +++++++++++++---- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml index a699eacb93c..90417fffe51 100644 --- a/.github/workflows/backport-ci.yml +++ b/.github/workflows/backport-ci.yml @@ -15,11 +15,14 @@ # limitations under the License. name: Backport -run-name: Backport ${{ github.event.label.name }} (PR #${{ github.event.pull_request.number }}) +run-name: Backport (PR #${{ github.event.pull_request.number }}) on: pull_request: types: + - opened + - reopened + - synchronize - labeled permissions: @@ -27,17 +30,51 @@ permissions: pull-requests: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }}-${{ github.event.label.name }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: + detect: + name: Detect backport targets + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.targets.outputs.targets }} + has_targets: ${{ steps.targets.outputs.has_targets }} + steps: + - name: Collect release labels + id: targets + uses: actions/github-script@v8 + with: + script: | + const action = context.payload.action; + let targets = []; + + if (action === "labeled") { + const newLabel = context.payload.label && context.payload.label.name; + if (newLabel && /^release\/.+$/.test(newLabel)) { + targets = [newLabel]; + } + } else { + const labels = context.payload.pull_request.labels.map((l) => l.name); + targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); + } + + core.info(`event=${action} targets=${JSON.stringify(targets)}`); + core.setOutput("targets", JSON.stringify(targets)); + core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); + run: - if: ${{ startsWith(github.event.label.name, 'release/') }} - name: ${{ github.event.label.name }} + needs: detect + if: ${{ needs.detect.outputs.has_targets == 'true' }} + name: ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.detect.outputs.targets) }} uses: ./.github/workflows/reusable-build.yml with: checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head - backport_target_branch: ${{ github.event.label.name }} + 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: true diff --git a/.github/workflows/create-backport-pr.yml b/.github/workflows/create-backport-pr.yml index f77a116b2fd..c59dc2fad66 100644 --- a/.github/workflows/create-backport-pr.yml +++ b/.github/workflows/create-backport-pr.yml @@ -86,15 +86,32 @@ jobs: } ); - const greenTargets = requestedTargets.filter((target) => { - const expectedTitle = `Backport ${target} (PR #${pullRequest.number})`; - return backportRuns.some( - (run) => - run.display_title === expectedTitle && - run.status === "completed" && - run.conclusion === "success" + const latestRun = backportRuns + .filter((r) => r.status === "completed") + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0]; + + let greenTargets = []; + if (!latestRun) { + core.warning(`No completed Backport workflow run found for ${pullRequest.head.sha}.`); + } else { + const jobs = await github.paginate( + github.rest.actions.listJobsForWorkflowRun, + { + owner, + repo, + run_id: latestRun.id, + per_page: 100, + } ); - }); + + greenTargets = requestedTargets.filter((target) => + jobs.some( + (job) => + job.name === `${target} / Build Summary` && + job.conclusion === "success" + ) + ); + } const skippedTargets = requestedTargets.filter((target) => !greenTargets.includes(target)); if (skippedTargets.length > 0) { From 13ce4b61af4970bfd62f6154805fceff6847f90b Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:16:08 -0700 Subject: [PATCH 11/27] ci: rename detect job to precheck The job is the entry point for every PR event and decides whether the matrix backport job runs at all, so 'precheck' reads more naturally than 'detect'. Behaviour is unchanged: precheck always runs, picks the just-added release/* label on labeled events or falls back to the PR's existing release/* labels otherwise, and the matrix run job is gated on precheck's has_targets output. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml index 90417fffe51..b0b3871687d 100644 --- a/.github/workflows/backport-ci.yml +++ b/.github/workflows/backport-ci.yml @@ -34,15 +34,15 @@ concurrency: cancel-in-progress: true jobs: - detect: - name: Detect backport targets + precheck: + name: Precheck runs-on: ubuntu-latest outputs: - targets: ${{ steps.targets.outputs.targets }} - has_targets: ${{ steps.targets.outputs.has_targets }} + targets: ${{ steps.decide.outputs.targets }} + has_targets: ${{ steps.decide.outputs.has_targets }} steps: - - name: Collect release labels - id: targets + - name: Decide backport targets + id: decide uses: actions/github-script@v8 with: script: | @@ -59,18 +59,23 @@ jobs: targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); } - core.info(`event=${action} targets=${JSON.stringify(targets)}`); + if (targets.length === 0) { + core.info(`No backport label on PR #${context.payload.pull_request.number}; skipping backport job.`); + } else { + core.info(`Backport targets for PR #${context.payload.pull_request.number}: ${targets.join(", ")}`); + } + core.setOutput("targets", JSON.stringify(targets)); core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); run: - needs: detect - if: ${{ needs.detect.outputs.has_targets == 'true' }} + needs: precheck + if: ${{ needs.precheck.outputs.has_targets == 'true' }} name: ${{ matrix.target }} strategy: fail-fast: false matrix: - target: ${{ fromJson(needs.detect.outputs.targets) }} + target: ${{ fromJson(needs.precheck.outputs.targets) }} uses: ./.github/workflows/reusable-build.yml with: checkout_ref: refs/pull/${{ github.event.pull_request.number }}/head From 2d0cc5b7652a3397e7d8a3416b2681f9b8012350 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:17:57 -0700 Subject: [PATCH 12/27] ci: gate main build jobs on a precheck routing job Add a precheck job to the Build workflow that emits run_frontend, run_scala, run_python, and run_agent_service outputs, and gate each stack's job on the matching output. The decisions currently default to true so behaviour is unchanged, but the structure is now in place to skip stacks based on changed paths or labels later. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 3d373313bd6..dcdc2069e7c 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -34,7 +34,37 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: + precheck: + name: Precheck + runs-on: ubuntu-latest + outputs: + run_frontend: ${{ steps.decide.outputs.run_frontend }} + run_scala: ${{ steps.decide.outputs.run_scala }} + run_python: ${{ steps.decide.outputs.run_python }} + run_agent_service: ${{ steps.decide.outputs.run_agent_service }} + steps: + - name: Decide which build jobs to run + id: decide + uses: actions/github-script@v8 + with: + script: | + // Default: run everything. Replace these with path-based or label-based + // logic if we want to skip stacks selectively. + const decisions = { + run_frontend: true, + run_scala: true, + run_python: true, + run_agent_service: true, + }; + + for (const [key, value] of Object.entries(decisions)) { + core.info(`${key}=${value}`); + core.setOutput(key, value ? "true" : "false"); + } + frontend: + needs: precheck + if: ${{ needs.precheck.outputs.run_frontend == 'true' }} name: frontend (${{ matrix.os }}, 18) runs-on: ${{ matrix.os }} strategy: @@ -80,6 +110,8 @@ jobs: run: yarn --cwd frontend run build:ci scala: + needs: precheck + if: ${{ needs.precheck.outputs.run_scala == 'true' }} strategy: matrix: os: [ubuntu-22.04] @@ -149,6 +181,8 @@ jobs: run: sbt test python: + needs: precheck + if: ${{ needs.precheck.outputs.run_python == 'true' }} strategy: matrix: os: [ubuntu-latest] @@ -181,6 +215,8 @@ jobs: cd amber/src/main/python && pytest -sv agent-service: + needs: precheck + if: ${{ needs.precheck.outputs.run_agent_service == 'true' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From b25cb9bb96aad262a60170bb75b724dd88b235a1 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:35:33 -0700 Subject: [PATCH 13/27] ci: clean up stale Backport runs on unlabeled events Add an unlabeled trigger and a cleanup-stale-runs job that, when a release/* label is removed, deletes prior Backport workflow runs for the PR head SHA so their failing/obsolete checks disappear from the PR's check list. Precheck still re-validates the remaining release/* labels so the leftover targets get a fresh, current run. Requires actions: write to call deleteWorkflowRun. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 46 ++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml index b0b3871687d..f7a188b8486 100644 --- a/.github/workflows/backport-ci.yml +++ b/.github/workflows/backport-ci.yml @@ -24,8 +24,10 @@ on: - reopened - synchronize - labeled + - unlabeled permissions: + actions: write contents: read pull-requests: read @@ -34,6 +36,44 @@ concurrency: cancel-in-progress: true jobs: + cleanup-stale-runs: + name: Cleanup stale backport runs + if: ${{ github.event.action == 'unlabeled' && startsWith(github.event.label.name, 'release/') }} + runs-on: ubuntu-latest + steps: + - name: Delete prior Backport runs for this PR head + uses: actions/github-script@v8 + with: + script: | + const { owner, repo } = context.repo; + const headSha = context.payload.pull_request.head.sha; + const currentRunId = context.runId; + + const runs = await github.paginate( + github.rest.actions.listWorkflowRuns, + { + owner, + repo, + workflow_id: "backport-ci.yml", + head_sha: headSha, + per_page: 100, + } + ); + + for (const run of runs) { + if (run.id === currentRunId) continue; + core.info(`Deleting backport run ${run.id} (${run.display_title})`); + try { + await github.rest.actions.deleteWorkflowRun({ + owner, + repo, + run_id: run.id, + }); + } catch (e) { + core.warning(`Failed to delete run ${run.id}: ${e.message}`); + } + } + precheck: name: Precheck runs-on: ubuntu-latest @@ -55,12 +95,16 @@ jobs: targets = [newLabel]; } } else { + // opened/reopened/synchronize/unlabeled - re-validate every + // remaining release/* label so the PR ends up with a clean, + // current set of backport checks (cleanup-stale-runs deletes + // the old ones for unlabeled events). const labels = context.payload.pull_request.labels.map((l) => l.name); targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); } if (targets.length === 0) { - core.info(`No backport label on PR #${context.payload.pull_request.number}; skipping backport job.`); + core.info(`No backport target for action=${action} on PR #${context.payload.pull_request.number}; skipping backport job.`); } else { core.info(`Backport targets for PR #${context.payload.pull_request.number}: ${targets.join(", ")}`); } From 843265afb0262ea4367c176eece15d0b7e333d5a Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:38:07 -0700 Subject: [PATCH 14/27] ci: rename create-backport-pr.yml to direct-backport-push.yml The workflow does not create a PR; it cherry-picks the merge commit directly onto the labeled release branch and pushes. The file name now matches the workflow's existing 'Direct Backport Push' name. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../{create-backport-pr.yml => direct-backport-push.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{create-backport-pr.yml => direct-backport-push.yml} (100%) diff --git a/.github/workflows/create-backport-pr.yml b/.github/workflows/direct-backport-push.yml similarity index 100% rename from .github/workflows/create-backport-pr.yml rename to .github/workflows/direct-backport-push.yml From f7e0568f557d480ee82e2a302eb458456db1c9e5 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:43:35 -0700 Subject: [PATCH 15/27] ci: merge backport CI into the Build workflow Move the backport precheck logic, matrix run job, and PR-event triggers into github-action-build.yml so a single Precheck decides both which main build stacks to run and which release/* targets need backport CI. The Build workflow now triggers on labeled and unlabeled PR events as well; main stacks short-circuit on label-only events because the code is unchanged. Update direct-backport-push.yml to look up backport status under the merged Build workflow: list all Build runs for the PR head SHA, collect their jobs, and match per-target Build Summary jobs by the matrix-prefixed name. Drop the standalone backport-ci.yml. Cleanup-stale-runs is intentionally not carried over for now; on the merged workflow, deleting whole runs would also drop main build results, which is unsafe. We can add a per-check_run cancellation later if needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-ci.yml | 133 --------------------- .github/workflows/direct-backport-push.yml | 38 +++--- .github/workflows/github-action-build.yml | 77 ++++++++++-- 3 files changed, 86 insertions(+), 162 deletions(-) delete mode 100644 .github/workflows/backport-ci.yml diff --git a/.github/workflows/backport-ci.yml b/.github/workflows/backport-ci.yml deleted file mode 100644 index f7a188b8486..00000000000 --- a/.github/workflows/backport-ci.yml +++ /dev/null @@ -1,133 +0,0 @@ -# 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. - -name: Backport -run-name: Backport (PR #${{ github.event.pull_request.number }}) - -on: - pull_request: - types: - - opened - - reopened - - synchronize - - labeled - - unlabeled - -permissions: - actions: write - contents: read - pull-requests: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - cleanup-stale-runs: - name: Cleanup stale backport runs - if: ${{ github.event.action == 'unlabeled' && startsWith(github.event.label.name, 'release/') }} - runs-on: ubuntu-latest - steps: - - name: Delete prior Backport runs for this PR head - uses: actions/github-script@v8 - with: - script: | - const { owner, repo } = context.repo; - const headSha = context.payload.pull_request.head.sha; - const currentRunId = context.runId; - - const runs = await github.paginate( - github.rest.actions.listWorkflowRuns, - { - owner, - repo, - workflow_id: "backport-ci.yml", - head_sha: headSha, - per_page: 100, - } - ); - - for (const run of runs) { - if (run.id === currentRunId) continue; - core.info(`Deleting backport run ${run.id} (${run.display_title})`); - try { - await github.rest.actions.deleteWorkflowRun({ - owner, - repo, - run_id: run.id, - }); - } catch (e) { - core.warning(`Failed to delete run ${run.id}: ${e.message}`); - } - } - - precheck: - name: Precheck - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.decide.outputs.targets }} - has_targets: ${{ steps.decide.outputs.has_targets }} - steps: - - name: Decide backport targets - id: decide - uses: actions/github-script@v8 - with: - script: | - const action = context.payload.action; - let targets = []; - - if (action === "labeled") { - const newLabel = context.payload.label && context.payload.label.name; - if (newLabel && /^release\/.+$/.test(newLabel)) { - targets = [newLabel]; - } - } else { - // opened/reopened/synchronize/unlabeled - re-validate every - // remaining release/* label so the PR ends up with a clean, - // current set of backport checks (cleanup-stale-runs deletes - // the old ones for unlabeled events). - const labels = context.payload.pull_request.labels.map((l) => l.name); - targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); - } - - if (targets.length === 0) { - core.info(`No backport target for action=${action} on PR #${context.payload.pull_request.number}; skipping backport job.`); - } else { - core.info(`Backport targets for PR #${context.payload.pull_request.number}: ${targets.join(", ")}`); - } - - core.setOutput("targets", JSON.stringify(targets)); - core.setOutput("has_targets", targets.length > 0 ? "true" : "false"); - - run: - needs: precheck - if: ${{ needs.precheck.outputs.has_targets == 'true' }} - name: ${{ matrix.target }} - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.precheck.outputs.targets) }} - uses: ./.github/workflows/reusable-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: true - run_scala: true - run_python: true - run_agent_service: true - secrets: inherit diff --git a/.github/workflows/direct-backport-push.yml b/.github/workflows/direct-backport-push.yml index c59dc2fad66..d1d37f27770 100644 --- a/.github/workflows/direct-backport-push.yml +++ b/.github/workflows/direct-backport-push.yml @@ -75,39 +75,39 @@ jobs: return; } - const backportRuns = await github.paginate( + const buildRuns = await github.paginate( github.rest.actions.listWorkflowRuns, { owner, repo, - workflow_id: "backport-ci.yml", + workflow_id: "github-action-build.yml", head_sha: pullRequest.head.sha, per_page: 100, } ); - const latestRun = backportRuns - .filter((r) => r.status === "completed") - .sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0]; - let greenTargets = []; - if (!latestRun) { - core.warning(`No completed Backport workflow run found for ${pullRequest.head.sha}.`); + if (buildRuns.length === 0) { + core.warning(`No Build workflow runs found for ${pullRequest.head.sha}.`); } else { - const jobs = await github.paginate( - github.rest.actions.listJobsForWorkflowRun, - { - owner, - repo, - run_id: latestRun.id, - per_page: 100, - } - ); + 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); + } greenTargets = requestedTargets.filter((target) => - jobs.some( + allJobs.some( (job) => - job.name === `${target} / Build Summary` && + job.name === `backport (${target}) / Build Summary` && job.conclusion === "success" ) ); diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index dcdc2069e7c..7d6377a0780 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -27,8 +27,18 @@ on: - 'main' - 'release/**' pull_request: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled workflow_dispatch: +permissions: + contents: read + pull-requests: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -42,26 +52,54 @@ jobs: run_scala: ${{ steps.decide.outputs.run_scala }} run_python: ${{ steps.decide.outputs.run_python }} run_agent_service: ${{ steps.decide.outputs.run_agent_service }} + backport_targets: ${{ steps.decide.outputs.backport_targets }} + has_backport_targets: ${{ steps.decide.outputs.has_backport_targets }} steps: - - name: Decide which build jobs to run + - name: Decide which jobs to run id: decide uses: actions/github-script@v8 with: script: | - // Default: run everything. Replace these with path-based or label-based - // logic if we want to skip stacks selectively. - const decisions = { - run_frontend: true, - run_scala: true, - run_python: true, - run_agent_service: true, - }; + const eventName = context.eventName; + const action = context.payload.action || ""; + const isLabelOnlyEvent = + eventName === "pull_request" && (action === "labeled" || action === "unlabeled"); - for (const [key, value] of Object.entries(decisions)) { + // Main build stacks. Default: run all. Skip on label-only PR + // events because the code did not change. + const stacks = { + run_frontend: !isLabelOnlyEvent, + run_scala: !isLabelOnlyEvent, + run_python: !isLabelOnlyEvent, + run_agent_service: !isLabelOnlyEvent, + }; + for (const [key, value] of Object.entries(stacks)) { core.info(`${key}=${value}`); core.setOutput(key, value ? "true" : "false"); } + // Backport targets. Only on PR events. + let targets = []; + if (eventName === "pull_request") { + if (action === "labeled") { + const newLabel = context.payload.label && context.payload.label.name; + if (newLabel && /^release\/.+$/.test(newLabel)) { + targets = [newLabel]; + } + } else if (action !== "unlabeled") { + const labels = context.payload.pull_request.labels.map((l) => l.name); + targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); + } + } + + if (targets.length === 0) { + core.info(`No backport targets for event=${eventName} action=${action || "(none)"}.`); + } else { + core.info(`Backport targets: ${targets.join(", ")}`); + } + core.setOutput("backport_targets", JSON.stringify(targets)); + core.setOutput("has_backport_targets", targets.length > 0 ? "true" : "false"); + frontend: needs: precheck if: ${{ needs.precheck.outputs.run_frontend == 'true' }} @@ -241,3 +279,22 @@ jobs: run: bun run typecheck - name: Run unit tests run: bun test + + backport: + needs: precheck + if: ${{ needs.precheck.outputs.has_backport_targets == 'true' }} + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.precheck.outputs.backport_targets) }} + uses: ./.github/workflows/reusable-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: true + run_scala: true + run_python: true + run_agent_service: true + secrets: inherit From d02693ef967e740e832a09cfce1fb29e279170f8 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 01:45:47 -0700 Subject: [PATCH 16/27] ci: drop has_backport_targets, gate on JSON string instead The precheck only ever needs to know whether the targets array is empty, and the matrix already reads backport_targets via fromJson. Replace the redundant has_backport_targets boolean with a direct "!= '[]'" check on the JSON output. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 7d6377a0780..73a836a5387 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -53,7 +53,6 @@ jobs: run_python: ${{ steps.decide.outputs.run_python }} run_agent_service: ${{ steps.decide.outputs.run_agent_service }} backport_targets: ${{ steps.decide.outputs.backport_targets }} - has_backport_targets: ${{ steps.decide.outputs.has_backport_targets }} steps: - name: Decide which jobs to run id: decide @@ -98,7 +97,6 @@ jobs: core.info(`Backport targets: ${targets.join(", ")}`); } core.setOutput("backport_targets", JSON.stringify(targets)); - core.setOutput("has_backport_targets", targets.length > 0 ? "true" : "false"); frontend: needs: precheck @@ -282,7 +280,7 @@ jobs: backport: needs: precheck - if: ${{ needs.precheck.outputs.has_backport_targets == 'true' }} + if: ${{ needs.precheck.outputs.backport_targets != '[]' }} strategy: fail-fast: false matrix: From d18971ab5b0768f9a1de799c55103a345f3301fe Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 02:05:40 -0700 Subject: [PATCH 17/27] ci: clean up build/backport job naming Drop the explicit name on the main frontend job so its skipped state no longer surfaces an unevaluated matrix placeholder. Give the backport matrix job a name of 'Backport ' so the middle segment of every backport check makes the target obvious and the checks visibly differ from the main build's flat names. Add a run_summary input to reusable-build.yml and pass false from the backport call; the per-target Build Summary check is redundant once we look up backport status by inspecting all matrix jobs for the target. Update direct-backport-push.yml accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/direct-backport-push.yml | 12 +++++------- .github/workflows/github-action-build.yml | 3 ++- .github/workflows/reusable-build.yml | 6 +++++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/direct-backport-push.yml b/.github/workflows/direct-backport-push.yml index d1d37f27770..4875f85bf35 100644 --- a/.github/workflows/direct-backport-push.yml +++ b/.github/workflows/direct-backport-push.yml @@ -104,13 +104,11 @@ jobs: allJobs.push(...jobs); } - greenTargets = requestedTargets.filter((target) => - allJobs.some( - (job) => - job.name === `backport (${target}) / Build Summary` && - job.conclusion === "success" - ) - ); + greenTargets = requestedTargets.filter((target) => { + const prefix = `Backport ${target} / `; + const targetJobs = allJobs.filter((job) => job.name.startsWith(prefix)); + return targetJobs.length > 0 && targetJobs.every((job) => job.conclusion === "success"); + }); } const skippedTargets = requestedTargets.filter((target) => !greenTargets.includes(target)); diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 73a836a5387..5918178b580 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -101,7 +101,6 @@ jobs: frontend: needs: precheck if: ${{ needs.precheck.outputs.run_frontend == 'true' }} - name: frontend (${{ matrix.os }}, 18) runs-on: ${{ matrix.os }} strategy: matrix: @@ -281,6 +280,7 @@ jobs: backport: needs: precheck if: ${{ needs.precheck.outputs.backport_targets != '[]' }} + name: Backport ${{ matrix.target }} strategy: fail-fast: false matrix: @@ -295,4 +295,5 @@ jobs: run_scala: true run_python: true run_agent_service: true + run_summary: false secrets: inherit diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 3f11b295cc1..9b2dce9885f 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -52,6 +52,10 @@ on: required: false type: boolean default: true + run_summary: + required: false + type: boolean + default: true summary_job_name: required: false type: string @@ -269,7 +273,7 @@ jobs: summary: name: ${{ inputs.summary_job_name }} - if: ${{ always() }} + if: ${{ always() && inputs.run_summary }} needs: - frontend - scala From 1fd1de79ed66388212145a20e10c3e2bd67da78c Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 02:08:18 -0700 Subject: [PATCH 18/27] ci: remove summary job from reusable-build The only caller is the merged Build workflow's backport job, which already aggregates per-target results in direct-backport-push.yml by checking every matrix job. Drop the summary job and its run_summary and summary_job_name inputs entirely instead of gating it off. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 1 - .github/workflows/reusable-build.yml | 43 ----------------------- 2 files changed, 44 deletions(-) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 5918178b580..18acbd38e97 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -295,5 +295,4 @@ jobs: run_scala: true run_python: true run_agent_service: true - run_summary: false secrets: inherit diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 9b2dce9885f..3e34206f90f 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -52,14 +52,6 @@ on: required: false type: boolean default: true - run_summary: - required: false - type: boolean - default: true - summary_job_name: - required: false - type: string - default: "Build Summary" env: NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} @@ -270,38 +262,3 @@ jobs: run: bun run typecheck - name: Run unit tests run: bun test - - summary: - name: ${{ inputs.summary_job_name }} - if: ${{ always() && inputs.run_summary }} - needs: - - frontend - - scala - - python - - agent-service - runs-on: ubuntu-latest - steps: - - name: Fail if any CI job failed - env: - FRONTEND_RESULT: ${{ needs.frontend.result }} - SCALA_RESULT: ${{ needs.scala.result }} - PYTHON_RESULT: ${{ needs.python.result }} - AGENT_SERVICE_RESULT: ${{ needs.agent-service.result }} - RUN_FRONTEND: ${{ inputs.run_frontend }} - RUN_SCALA: ${{ inputs.run_scala }} - RUN_PYTHON: ${{ inputs.run_python }} - RUN_AGENT_SERVICE: ${{ inputs.run_agent_service }} - run: | - check_result() { - local enabled="$1" - local result="$2" - if [[ "$enabled" == "true" && "$result" != "success" ]]; then - echo "A required CI job failed: $result" - exit 1 - fi - } - - check_result "$RUN_FRONTEND" "$FRONTEND_RESULT" - check_result "$RUN_SCALA" "$SCALA_RESULT" - check_result "$RUN_PYTHON" "$PYTHON_RESULT" - check_result "$RUN_AGENT_SERVICE" "$AGENT_SERVICE_RESULT" From bc560eb9571f304004bd0c4c82e47724553891d6 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 02:36:47 -0700 Subject: [PATCH 19/27] ci: drop explicit name on backport matrix jobs When the matrix is empty (no release/* targets or no green targets), GHA still surfaces the parent job as Skipped using the literal name template, which leaves '${{ matrix.target }}' showing in the PR checks list. Remove the explicit name on both the Build workflow's backport job and the direct-backport-push push-backports job; the default ' ()' display works when the matrix runs and collapses to just the job id when skipped. Update the lookup pattern in direct-backport-push.yml to match the new 'backport () /' job name format. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/direct-backport-push.yml | 3 +-- .github/workflows/github-action-build.yml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/direct-backport-push.yml b/.github/workflows/direct-backport-push.yml index 4875f85bf35..182c5d5d78c 100644 --- a/.github/workflows/direct-backport-push.yml +++ b/.github/workflows/direct-backport-push.yml @@ -105,7 +105,7 @@ jobs: } greenTargets = requestedTargets.filter((target) => { - const prefix = `Backport ${target} / `; + const prefix = `backport (${target}) / `; const targetJobs = allJobs.filter((job) => job.name.startsWith(prefix)); return targetJobs.length > 0 && targetJobs.every((job) => job.conclusion === "success"); }); @@ -121,7 +121,6 @@ jobs: core.setOutput("has_targets", greenTargets.length > 0 ? "true" : "false"); push-backports: - name: Push backport to ${{ matrix.target }} needs: discover if: ${{ needs.discover.outputs.has_targets == 'true' }} runs-on: ubuntu-latest diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 18acbd38e97..5fc6b166fb2 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -280,7 +280,6 @@ jobs: backport: needs: precheck if: ${{ needs.precheck.outputs.backport_targets != '[]' }} - name: Backport ${{ matrix.target }} strategy: fail-fast: false matrix: From 770b548268968bfa407784899d2764ccfedb58e7 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 02:46:13 -0700 Subject: [PATCH 20/27] ci: split backport cleanup into its own workflow on unlabeled Drop unlabeled from the Build workflow's pull_request triggers so a release/* label removal no longer kicks off a fresh Build run that would surface skipped frontend/scala/python/agent-service checks; in- flight or completed main build jobs from prior runs keep their state untouched. Add backport-cleanup.yml that fires on unlabeled events for release/* labels. For each Build workflow run on the PR head SHA, if the run has backport jobs for the removed target and no main build output worth keeping (label-event run with main stacks all skipped), cancel and delete the whole run. Otherwise, patch the per-target backport check_runs to status=completed conclusion=cancelled so the PR's check list stops showing the obsolete failures while the run's main build entries stay intact. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-cleanup.yml | 110 ++++++++++++++++++++++ .github/workflows/github-action-build.yml | 5 +- 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/backport-cleanup.yml diff --git a/.github/workflows/backport-cleanup.yml b/.github/workflows/backport-cleanup.yml new file mode 100644 index 00000000000..d4527b0b1dd --- /dev/null +++ b/.github/workflows/backport-cleanup.yml @@ -0,0 +1,110 @@ +# 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. + +name: Backport Cleanup +run-name: Backport cleanup ${{ github.event.label.name }} (PR #${{ github.event.pull_request.number }}) + +on: + pull_request: + types: + - unlabeled + +permissions: + actions: write + checks: write + contents: read + pull-requests: read + +jobs: + cleanup: + if: ${{ startsWith(github.event.label.name, 'release/') }} + runs-on: ubuntu-latest + steps: + - name: Cancel and clear backport checks for the removed target + uses: actions/github-script@v8 + 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 runs = await github.paginate( + github.rest.actions.listWorkflowRuns, + { + owner, + repo, + workflow_id: "github-action-build.yml", + head_sha: headSha, + per_page: 100, + } + ); + + let patchedChecks = false; + + for (const run of runs) { + const jobs = await github.paginate( + github.rest.actions.listJobsForWorkflowRun, + { owner, repo, run_id: run.id, per_page: 100 } + ); + + const hasTargetBackport = jobs.some((j) => j.name.startsWith(prefix)); + if (!hasTargetBackport) continue; + + const mainBuildJobs = jobs.filter( + (j) => !j.name.startsWith("backport") && j.name !== "Precheck" + ); + const mainAllSkipped = + mainBuildJobs.length === 0 || + mainBuildJobs.every((j) => j.conclusion === "skipped"); + + if (mainAllSkipped) { + core.info(`Run ${run.id} only had backport for ${target}; deleting.`); + if (run.status !== "completed") { + try { + await github.rest.actions.cancelWorkflowRun({ owner, repo, run_id: run.id }); + } catch (e) { + core.warning(`Failed to cancel run ${run.id}: ${e.message}`); + } + } + try { + await github.rest.actions.deleteWorkflowRun({ owner, repo, run_id: run.id }); + } catch (e) { + core.warning(`Failed to delete run ${run.id}: ${e.message}`); + } + } else if (!patchedChecks) { + core.info(`Run ${run.id} has main build results; patching backport check_runs for ${target} to cancelled.`); + 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; + try { + await github.rest.checks.update({ + owner, + repo, + check_run_id: check.id, + status: "completed", + conclusion: "cancelled", + }); + } catch (e) { + core.warning(`Failed to update check ${check.id} (${check.name}): ${e.message}`); + } + } + patchedChecks = true; + } + } diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 5fc6b166fb2..e6389dfbd59 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -32,7 +32,6 @@ on: - reopened - synchronize - labeled - - unlabeled workflow_dispatch: permissions: @@ -62,7 +61,7 @@ jobs: const eventName = context.eventName; const action = context.payload.action || ""; const isLabelOnlyEvent = - eventName === "pull_request" && (action === "labeled" || action === "unlabeled"); + eventName === "pull_request" && action === "labeled"; // Main build stacks. Default: run all. Skip on label-only PR // events because the code did not change. @@ -85,7 +84,7 @@ jobs: if (newLabel && /^release\/.+$/.test(newLabel)) { targets = [newLabel]; } - } else if (action !== "unlabeled") { + } else { const labels = context.payload.pull_request.labels.map((l) => l.name); targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); } From 82c87046b68ea9f84c41103204e6d70dc5fdaf91 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 02:54:48 -0700 Subject: [PATCH 21/27] ci: pin frontend display name to 18 for ASF YAML parity The Apache YAML/branch protection refers to the frontend check as 'frontend (, 18)'. Reinstate the explicit name on the main build's frontend matrix job so its check name matches the upstream expectation, even though the matrix actually uses node-version 20.19.0 today. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index e6389dfbd59..b5a3fe48e6e 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -100,6 +100,7 @@ jobs: frontend: needs: precheck if: ${{ needs.precheck.outputs.run_frontend == 'true' }} + name: frontend (${{ matrix.os }}, 18) runs-on: ${{ matrix.os }} strategy: matrix: From 7989b89466da5bdd4e9aced206724d54d49ce1fc Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 03:04:18 -0700 Subject: [PATCH 22/27] ci: re-run full Build on labeled and unlabeled events Drop the backport-cleanup workflow and revert to a single Build workflow that fires a full run on every relevant PR event, including labeled and unlabeled. Precheck always enables every main build stack, and backport targets reflect the PR's current release/* label set, so adding or removing a label simply re-runs the full matrix with the up-to-date target list. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/backport-cleanup.yml | 110 ---------------------- .github/workflows/github-action-build.yml | 34 ++----- 2 files changed, 9 insertions(+), 135 deletions(-) delete mode 100644 .github/workflows/backport-cleanup.yml diff --git a/.github/workflows/backport-cleanup.yml b/.github/workflows/backport-cleanup.yml deleted file mode 100644 index d4527b0b1dd..00000000000 --- a/.github/workflows/backport-cleanup.yml +++ /dev/null @@ -1,110 +0,0 @@ -# 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. - -name: Backport Cleanup -run-name: Backport cleanup ${{ github.event.label.name }} (PR #${{ github.event.pull_request.number }}) - -on: - pull_request: - types: - - unlabeled - -permissions: - actions: write - checks: write - contents: read - pull-requests: read - -jobs: - cleanup: - if: ${{ startsWith(github.event.label.name, 'release/') }} - runs-on: ubuntu-latest - steps: - - name: Cancel and clear backport checks for the removed target - uses: actions/github-script@v8 - 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 runs = await github.paginate( - github.rest.actions.listWorkflowRuns, - { - owner, - repo, - workflow_id: "github-action-build.yml", - head_sha: headSha, - per_page: 100, - } - ); - - let patchedChecks = false; - - for (const run of runs) { - const jobs = await github.paginate( - github.rest.actions.listJobsForWorkflowRun, - { owner, repo, run_id: run.id, per_page: 100 } - ); - - const hasTargetBackport = jobs.some((j) => j.name.startsWith(prefix)); - if (!hasTargetBackport) continue; - - const mainBuildJobs = jobs.filter( - (j) => !j.name.startsWith("backport") && j.name !== "Precheck" - ); - const mainAllSkipped = - mainBuildJobs.length === 0 || - mainBuildJobs.every((j) => j.conclusion === "skipped"); - - if (mainAllSkipped) { - core.info(`Run ${run.id} only had backport for ${target}; deleting.`); - if (run.status !== "completed") { - try { - await github.rest.actions.cancelWorkflowRun({ owner, repo, run_id: run.id }); - } catch (e) { - core.warning(`Failed to cancel run ${run.id}: ${e.message}`); - } - } - try { - await github.rest.actions.deleteWorkflowRun({ owner, repo, run_id: run.id }); - } catch (e) { - core.warning(`Failed to delete run ${run.id}: ${e.message}`); - } - } else if (!patchedChecks) { - core.info(`Run ${run.id} has main build results; patching backport check_runs for ${target} to cancelled.`); - 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; - try { - await github.rest.checks.update({ - owner, - repo, - check_run_id: check.id, - status: "completed", - conclusion: "cancelled", - }); - } catch (e) { - core.warning(`Failed to update check ${check.id} (${check.name}): ${e.message}`); - } - } - patchedChecks = true; - } - } diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index b5a3fe48e6e..0f8f5fd754c 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -32,6 +32,7 @@ on: - reopened - synchronize - labeled + - unlabeled workflow_dispatch: permissions: @@ -59,39 +60,22 @@ jobs: with: script: | const eventName = context.eventName; - const action = context.payload.action || ""; - const isLabelOnlyEvent = - eventName === "pull_request" && action === "labeled"; - // Main build stacks. Default: run all. Skip on label-only PR - // events because the code did not change. - const stacks = { - run_frontend: !isLabelOnlyEvent, - run_scala: !isLabelOnlyEvent, - run_python: !isLabelOnlyEvent, - run_agent_service: !isLabelOnlyEvent, - }; - for (const [key, value] of Object.entries(stacks)) { - core.info(`${key}=${value}`); - core.setOutput(key, value ? "true" : "false"); + // Main build stacks: always run. + const stacks = ["run_frontend", "run_scala", "run_python", "run_agent_service"]; + for (const key of stacks) { + core.setOutput(key, "true"); } - // Backport targets. Only on PR events. + // Backport targets: all current release/* labels on the PR. let targets = []; if (eventName === "pull_request") { - if (action === "labeled") { - const newLabel = context.payload.label && context.payload.label.name; - if (newLabel && /^release\/.+$/.test(newLabel)) { - targets = [newLabel]; - } - } else { - const labels = context.payload.pull_request.labels.map((l) => l.name); - targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); - } + const labels = context.payload.pull_request.labels.map((l) => l.name); + targets = [...new Set(labels.filter((n) => /^release\/.+$/.test(n)))].sort(); } if (targets.length === 0) { - core.info(`No backport targets for event=${eventName} action=${action || "(none)"}.`); + core.info(`No backport targets on PR.`); } else { core.info(`Backport targets: ${targets.join(", ")}`); } From 18a0558f8de5a05470ed22dafaf4e88880938ff8 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 03:08:17 -0700 Subject: [PATCH 23/27] ci: clean up obsolete backport checks on unlabeled Add a cleanup-stale-backport job to the Build workflow that fires only on pull_request unlabeled events for release/* labels. It patches every check_run on the PR head SHA whose name starts with 'backport () ' to status=completed conclusion=cancelled, so the PR's check list stops surfacing the obsolete target's failed/successful entries while the rest of the Build run (precheck + main stacks + backport for remaining targets) still re-runs in full as the user expects. Add checks: write to the workflow permissions to allow the patch. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 0f8f5fd754c..db6da4da140 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -36,6 +36,7 @@ on: workflow_dispatch: permissions: + checks: write contents: read pull-requests: read @@ -81,6 +82,41 @@ jobs: } 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@v8 + 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}`); + } + } + frontend: needs: precheck if: ${{ needs.precheck.outputs.run_frontend == 'true' }} From 28099c415bb26e7a3205c9158350f678bfc50645 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 09:55:52 -0700 Subject: [PATCH 24/27] ci: squash the PR range before cherry-picking onto release The pre-merge backport step used to cherry-pick every commit in the PR's range one by one. When the release branch already contains some of those changes under different SHAs (e.g. a prior manual backport), the per-commit picks can hit spurious conflicts even though the cumulative diff would apply cleanly. Build an artificial squash commit (parent = range start, tree = range end) and cherry-pick that single commit instead. The 3-way merge between the PR's base, the PR's head tree, and the release tip lets git collapse already-applied parts to no-ops and only applies what's actually new. This matches what direct-backport-push does post-merge for squash-merged PRs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/scripts/prepare-backport-checkout.sh | 22 ++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/scripts/prepare-backport-checkout.sh b/.github/scripts/prepare-backport-checkout.sh index a4d5391066f..5286275516a 100644 --- a/.github/scripts/prepare-backport-checkout.sh +++ b/.github/scripts/prepare-backport-checkout.sh @@ -26,15 +26,25 @@ git fetch --no-tags origin "${target_branch}" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" -commits=() -while IFS= read -r commit; do - commits+=("${commit}") -done < <(git rev-list --reverse "${commit_range}") +if [[ "${commit_range}" != *..* ]]; then + echo "Invalid commit range: ${commit_range}" >&2 + exit 1 +fi +start_sha="${commit_range%..*}" +end_sha="${commit_range##*..}" -if [[ "${#commits[@]}" -eq 0 ]]; then +if [[ -z "$(git rev-list -n 1 "${commit_range}")" ]]; then echo "No commits found in range ${commit_range}" >&2 exit 1 fi +# Build a single squash commit whose parent is the range start and whose tree +# matches the range end. Cherry-picking this squash onto the release branch +# applies the cumulative diff in one 3-way merge, which avoids spurious +# conflicts when intermediate commits in the range happen to overlap with +# changes already present (under different SHAs) on the release branch. +end_tree="$(git rev-parse "${end_sha}^{tree}")" +squash_sha="$(git commit-tree -p "${start_sha}" -m "ci: squashed backport of ${commit_range}" "${end_tree}")" + git checkout -B "${workspace_branch}" "origin/${target_branch}" -git cherry-pick -x "${commits[@]}" +git cherry-pick -x "${squash_sha}" From c557153e01a7abfc7a115df140873dbf89a07689 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 10:03:00 -0700 Subject: [PATCH 25/27] ci: use the PR title and description for backport commit message Replace 'git cherry-pick -x' with a no-commit pick followed by an explicit 'git commit -F -' that builds the message from the PR's title, body, and a cherry-picked footer. The release branch commit now carries the same description as the original PR instead of the auto-generated squash subject, which preserves intent and makes the release commit log self-documenting. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/direct-backport-push.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/direct-backport-push.yml b/.github/workflows/direct-backport-push.yml index 182c5d5d78c..149a5cbd0ec 100644 --- a/.github/workflows/direct-backport-push.yml +++ b/.github/workflows/direct-backport-push.yml @@ -137,6 +137,8 @@ jobs: env: MERGE_SHA: ${{ github.sha }} TARGET_BRANCH: ${{ matrix.target }} + PR_NUMBER: ${{ needs.discover.outputs.pr_number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail @@ -149,7 +151,19 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + pr_title=$(gh pr view "${PR_NUMBER}" --json title --jq .title) + pr_body=$(gh pr view "${PR_NUMBER}" --json body --jq .body) + git fetch --no-tags origin "${TARGET_BRANCH}" git checkout -B "${TARGET_BRANCH}" "origin/${TARGET_BRANCH}" - git cherry-pick -x "${MERGE_SHA}" + git cherry-pick --no-commit "${MERGE_SHA}" + + { + printf '%s (#%s)\n\n' "${pr_title}" "${PR_NUMBER}" + if [[ -n "${pr_body}" ]]; then + printf '%s\n\n' "${pr_body}" + fi + printf '(cherry picked from commit %s)\n' "${MERGE_SHA}" + } | git commit -F - + git push origin "HEAD:${TARGET_BRANCH}" From becbc2cd77d3a02c164a748080a4b95b984f34db Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 10:29:49 -0700 Subject: [PATCH 26/27] ci: revert unrelated formatting drift and tighten CONTRIBUTING Restore the bracket spacing and blank-line / Postgres comment in github-action-build.yml that the prior edits accidentally rewrote so the diff against main only shows the intended changes. Collapse the two release-label bullets in CONTRIBUTING.md into a single user-facing instruction with an example label name; drop the GitHub Actions implementation detail since contributors don't need it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 10 ++++++---- CONTRIBUTING.md | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 315fccb8e0c..4a68a24fc46 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -172,12 +172,13 @@ jobs: if: ${{ needs.precheck.outputs.run_scala == 'true' }} strategy: matrix: - os: [ubuntu-22.04] - java-version: [11] + os: [ ubuntu-22.04 ] + java-version: [ 11 ] runs-on: ${{ matrix.os }} env: JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + services: postgres: image: postgres @@ -185,6 +186,7 @@ jobs: POSTGRES_PASSWORD: postgres ports: - 5432:5432 + # Add a health check so steps wait until Postgres is ready options: >- --health-cmd="pg_isready -U postgres" --health-interval=10s @@ -278,8 +280,8 @@ jobs: if: ${{ needs.precheck.outputs.run_python == 'true' }} strategy: matrix: - os: [ubuntu-latest] - python-version: ['3.10', '3.11', '3.12', '3.13'] + os: [ ubuntu-latest ] + python-version: [ '3.10', '3.11', '3.12', '3.13' ] runs-on: ${{ matrix.os }} steps: - name: Checkout Texera diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b703f5ba48b..f31b0052e09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,8 +84,7 @@ yarn format:fix ### 4. PR Review - [ ] Ask a Texera Committer (by commenting on the PR) to triage your PR, i.e., request a reviewer, and assign the PR to you. - [ ] Add appropriate labels such as `fix`, `enhancement`, `docs`, etc. -- [ ] If the change should also land in a release branch, add the release branch label directly, such as `release/1.1`. -- [ ] When a `release/*` label is present, GitHub Actions will run the PR changes against that target release branch and, after the PR is squash-merged into `main`, automatically push the same change to the labeled release branch if the backport CI passed. +- [ ] If the change should also land in a release branch, add the matching `release/` label (e.g. `release/v1.1.0-incubating`); the change will be backported to that branch automatically. - [ ] Ensure that all CI checks pass (see [GitHub Actions](https://github.com/Texera/texera/actions)). - [ ] Fully test your changes locally. From 8e600fde70e3c2cb8723dd29f0ca8539658706d2 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Fri, 1 May 2026 11:11:57 -0700 Subject: [PATCH 27/27] ci: address PR review comments on github-action-build.yml - Restore the 2-space trailing whitespace on the env-block separator (per @bobbai00's "revert this" comment on line 22 of the original diff). - Add a top-of-job comment block on the precheck job documenting what each output drives (main build stack gates and the backport matrix's target list), per @bobbai00's request to clarify the job's purpose. The third review comment, asking to call reusable-build.yml from the main jobs, is intentionally deferred: the .asf.yaml ruleset requires exact check-name matching and using uses: would prefix the inner job names with the calling job, breaking the required check contexts. Tracked separately. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/github-action-build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-action-build.yml b/.github/workflows/github-action-build.yml index 4a68a24fc46..47e74f52cbe 100644 --- a/.github/workflows/github-action-build.yml +++ b/.github/workflows/github-action-build.yml @@ -19,7 +19,7 @@ name: Build env: NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} - + on: push: branches: @@ -45,6 +45,12 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: + # Precheck decides which downstream jobs run for this event: + # - run_frontend / run_scala / run_python / run_agent_service: gate the + # main build stacks. All true today; placeholder for future path- or + # label-based selection. + # - backport_targets: JSON array of release/* labels currently on the PR. + # Drives the backport matrix; empty array means no backport runs. precheck: name: Precheck runs-on: ubuntu-latest