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

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

commit a8bb1ec7c0747aaa08ac58ccd46f96724e3a7547
Author: Xinyuan Lin <[email protected]>
AuthorDate: Wed Jul 22 20:55:01 2026 -0700

    ci: route all Codecov flags through the deferred workflow_run upload (#6730)
    
    ### What changes were proposed in this PR?
    
    Route **every** Codecov flag's upload through a follow-up `workflow_run`
    workflow instead of uploading inline from the `pull_request` build, so
    fork PRs get authenticated (non-rate-limited) uploads and a reliable
    Codecov PR comment.
    
    **Problem.** A PR from a fork never receives `secrets.CODECOV_TOKEN`
    (the build runs on `pull_request`, and GitHub withholds secrets from
    fork runs). The inline `codecov-action` steps therefore fall back to
    Codecov's *tokenless* path, which is rate-limited; under a burst of
    fork-PR uploads Codecov still processes the report but silently drops
    the PR comment (reported on #6685).
    
    **Fix.**
    - `build.yml`: each coverage job stages its reports as a
    `codecov-<flag>` artifact via `.github/scripts/stage-codecov.sh`
    (`coverage/` + `results/` + pr/sha/branch identifiers) instead of
    uploading inline. The script preserves the directory tree so amber's ~8
    same-basename module `jacoco.xml` don't collide. Staging is skipped on
    backport builds (artifact-name collisions across the target matrix) and
    pinned to the single producing leg for the dual-OS / multi-python jobs.
    - `codecov-upload.yml`: a per-flag matrix runs on `workflow_run` after
    **Required Checks**, in the base-repo context where `CODECOV_TOKEN` is
    available. It downloads each artifact and re-uploads with the token,
    using `directory:` scoping so Codecov's search finds every file per
    flag.
    - **Failure surfacing:** coverage uploads fail hard, and a
    `notify-failure` job then comments on the PR (a link to the failed run +
    the `run_id` to manually dispatch) — Codecov only self-comments on
    success, so this covers the failure case. A delayed / no-runner run
    posts nothing at all, which is itself the signal that the upload is
    stuck.
    
    Flags covered: frontend, amber, amber-integration (test-results only),
    pyamber, agent-service, and the 6 platform services.
    
    Why not batch into fewer calls: Codecov requires one upload per flag
    (flags in a single upload apply to *all* its files, and carryforward —
    which `codecov.yml` relies on — needs the flag set at upload time), with
    no path-based auto-assignment, so the calls can't be collapsed without
    dropping the per-flag / carryforward model. The token — not batching —
    is what removes the tokenless rate-limit pressure.
    
    ### Any related issues, documentation, discussions?
    
    Reported on #6685.
    
    ### How was this PR tested?
    
    - `workflow_run` only triggers from the copy of the file on the default
    branch, so `codecov-upload.yml` activates only after merge; it exposes a
    `workflow_dispatch` (`run_id`) entry to validate against a finished
    *Required Checks* run immediately after merge.
    - `.github/scripts/stage-codecov.sh` was exercised locally: multi-module
    glob → no basename collisions, single-file flags, and the empty-PR
    (push) case.
    - YAML validated for both workflows. The staging half runs in **this
    PR's own CI**, so the `codecov-<flag>` artifacts can be inspected on
    this run.
    - Reviewed for coverage parity (every prior inline upload is still
    staged + re-uploaded — nothing dropped), artifact-name collisions
    (backport guard + per-leg pinning), and deferred-upload correctness
    (empty-override omission, missing-artifact handling, `matrix.coverage`
    gating).
    - First-live-run check after merge: confirm each flag's test-results
    (Test Analytics) JUnit files are discovered under `directory:` —
    best-effort, and separate from the coverage report.
    
    Since the deferred workflow can only activate on `main`, if anything is
    off the whole change reverts as a single PR.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Yes.
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .github/scripts/stage-codecov.sh     |  72 ++++++++++
 .github/workflows/build.yml          | 260 ++++++++++++++++++-----------------
 .github/workflows/codecov-upload.yml | 213 ++++++++++++++++++++++++++++
 3 files changed, 420 insertions(+), 125 deletions(-)

diff --git a/.github/scripts/stage-codecov.sh b/.github/scripts/stage-codecov.sh
new file mode 100644
index 0000000000..431993aad1
--- /dev/null
+++ b/.github/scripts/stage-codecov.sh
@@ -0,0 +1,72 @@
+#!/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.
+
+# Stage coverage + test-result reports for the deferred Codecov upload
+# (.github/workflows/codecov-upload.yml). Fork PRs get no CODECOV_TOKEN on the
+# pull_request event, so the build stages its reports as a `codecov-<flag>`
+# artifact instead of uploading inline; the workflow_run job re-uploads them 
with
+# a token (see codecov-upload.yml / #6685).
+#
+# Produces ./cc/coverage/ (coverage reports), ./cc/results/ (JUnit XMLs), and 
the
+# pr/sha/branch identifier files. Directory structure is preserved so 
same-basename
+# reports (e.g. amber's per-module jacoco.xml across ~8 modules) do not 
collide;
+# the deferred job points `directory:` at cc/coverage and cc/results so 
Codecov's
+# recursive search finds every file under each.
+#
+# Inputs (env):
+#   CC_COVERAGE  space-separated list of coverage sources — each a literal 
file or
+#                a `find -path` glob (use * which matches across /), e.g.
+#                "*/target/scala-2.13/jacoco/report/jacoco.xml". May be empty 
for a
+#                test-results-only flag.
+#   CC_RESULTS   same, for JUnit test-result XMLs.
+#   CC_PR        PR number   (empty on push builds)
+#   CC_SHA       head commit sha
+#   CC_BRANCH    head branch
+set -uo pipefail
+
+mkdir -p cc/coverage cc/results
+
+copy_into() {
+  dest="$1"; shift
+  for src in "$@"; do
+    if [ -e "$src" ]; then
+      # literal existing path
+      cp --parents "$src" "$dest/" 2>/dev/null || cp "$src" "$dest/" 
2>/dev/null || true
+    else
+      # treat as a find -path glob (matches across / since find's * does)
+      find . -path "./$src" -o -path "$src" 2>/dev/null \
+        | while IFS= read -r f; do
+            [ -f "$f" ] && cp --parents "$f" "$dest/" 2>/dev/null || true
+          done
+    fi
+  done
+}
+
+# shellcheck disable=SC2086  # intentional word-splitting into separate globs
+copy_into cc/coverage ${CC_COVERAGE:-}
+# shellcheck disable=SC2086
+copy_into cc/results ${CC_RESULTS:-}
+
+printf '%s' "${CC_PR:-}"     > cc/pr-number.txt
+printf '%s' "${CC_SHA:-}"    > cc/commit-sha.txt
+printf '%s' "${CC_BRANCH:-}" > cc/branch.txt
+
+echo "Staged coverage files:"
+find cc/coverage -type f 2>/dev/null | sed 's/^/  /' || true
+echo "Staged result files:"
+find cc/results -type f 2>/dev/null | sed 's/^/  /' || true
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1355a4dbbc..34eeff6ae3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -137,44 +137,36 @@ jobs:
         run: ./bin/licensing/check_binary_deps.py ${{ inputs.mode == 'PR' && 
'--ignore-transitive-version' || '' }} npm frontend/dist/3rdpartylicenses.json
       - name: Run frontend unit tests
         run: yarn --cwd frontend run test:ci
-      - name: Upload frontend coverage to Codecov
-        if: matrix.os == 'ubuntu-latest' && always()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./frontend/coverage/**/lcov.info
-          flags: frontend
-          fail_ci_if_error: false
-      - name: Upload frontend unit test results to Codecov
-        # vitest.config.ts adds a `junit` reporter that writes to junit.xml
-        # in the working dir; the @angular/build:unit-test runner forwards
-        # vitest config through unchanged.
-        if: matrix.os == 'ubuntu-latest' && !cancelled()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./frontend/junit.xml
-          flags: frontend
-          report_type: test_results
-          disable_search: true
-          fail_ci_if_error: false
       - name: Install Playwright Chromium
         run: yarn --cwd frontend playwright install ${{ matrix.os == 
'ubuntu-latest' && '--with-deps' || '' }} chromium
       - name: Run frontend browser-mode tests
         run: yarn --cwd frontend ng run gui:test-browser
-      - name: Upload frontend browser-mode test results to Codecov
-        # vitest.browser.config.ts emits junit-browser.xml (distinct from
-        # the unit-test report). Same `frontend` flag — Codecov merges
-        # multi-file uploads under one flag.
-        if: matrix.os == 'ubuntu-latest' && !cancelled()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+      # Codecov upload is deferred to codecov-upload.yml (workflow_run) so 
fork PRs
+      # get authenticated uploads and a reliable PR comment — see that file and
+      # #6685. Stage this flag's reports as the `codecov-frontend` artifact 
instead
+      # of uploading inline. The `!cancelled()` guard is a status-check 
function, so
+      # it overrides the implicit success() and staging still runs when the 
unit or
+      # browser tests fail (matching the old always()/!cancelled() steps); it 
only
+      # skips a cancelled run. Skipped on backport builds (different ref + 
would
+      # collide on the artifact name across the target matrix).
+      - name: Stage frontend coverage for deferred Codecov upload
+        if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == 
'' && !cancelled()
+        shell: bash
+        env:
+          CC_COVERAGE: frontend/coverage/gui/lcov.info
+          CC_RESULTS: frontend/junit.xml frontend/junit-browser.xml
+          CC_PR: ${{ github.event.pull_request.number }}
+          CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+          CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name 
}}
+        run: bash .github/scripts/stage-codecov.sh
+      - name: Upload frontend Codecov artifact
+        if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == 
'' && !cancelled()
+        uses: actions/upload-artifact@v4
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./frontend/junit-browser.xml
-          flags: frontend
-          report_type: test_results
-          disable_search: true
-          fail_ci_if_error: false
+          name: codecov-frontend
+          path: cc/
+          retention-days: 1
+          if-no-files-found: warn
 
   amber:
     # The amber job runs the cross-cutting Scala lints (scalafmtCheckAll,
@@ -300,28 +292,29 @@ jobs:
               "WorkflowCore/jacoco" \
               "WorkflowOperator/jacoco" \
               "WorkflowExecutionService/jacoco"
-      - name: Upload amber and common coverage to Codecov
-        if: always()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./**/target/scala-2.13/jacoco/report/jacoco.xml
-          flags: amber
-          fail_ci_if_error: false
-      - name: Upload amber and common test results to Codecov
-        # ScalaTest writes one JUnit-XML per spec under each module's
-        # target/test-reports/ (configured ThisBuild in build.sbt). Glob
-        # picks them up from every module that ran in the jacoco invocation
-        # above. `!cancelled()` so test failures still upload (the point
-        # of Test Analytics).
-        if: ${{ !cancelled() }}
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+      - name: Stage amber and common coverage for deferred Codecov upload
+        # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an
+        # authenticated upload — see #6685. Multi-module globs: the staging 
script
+        # preserves the directory tree so the ~8 same-basename jacoco.xml (and 
the
+        # per-spec test-reports) don't collide when flattened. `!cancelled()` 
keeps
+        # staging on test failure; skipped on backport builds (artifact-name 
clash).
+        if: inputs.backport_target_branch == '' && !cancelled()
+        shell: bash
+        env:
+          CC_COVERAGE: "*/target/scala-2.13/jacoco/report/jacoco.xml"
+          CC_RESULTS: "*/target/test-reports/*.xml"
+          CC_PR: ${{ github.event.pull_request.number }}
+          CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+          CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name 
}}
+        run: bash .github/scripts/stage-codecov.sh
+      - name: Upload amber Codecov artifact
+        if: inputs.backport_target_branch == '' && !cancelled()
+        uses: actions/upload-artifact@v4
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./**/target/test-reports/*.xml
-          flags: amber
-          report_type: test_results
-          fail_ci_if_error: false
+          name: codecov-amber
+          path: cc/
+          retention-days: 1
+          if-no-files-found: warn
 
   amber-integration:
     # Runs Scala tests tagged @org.apache.texera.amber.tags.IntegrationTest —
@@ -656,18 +649,30 @@ jobs:
         # --junit-xml feeds the Test Analytics upload below.
         run: |
           cd amber && pytest -m integration --junit-xml=junit-integration.xml 
-sv
-      - name: Upload amber integration test results to Codecov
-        # Two separate uploads because the ScalaTest and pytest runs each
-        # produce their own JUnit-XMLs and Codecov keys uploads by flag.
-        # `!cancelled()` so test failures still upload.
-        if: ${{ !cancelled() }}
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+      - name: Stage amber-integration test results for deferred Codecov upload
+        # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an
+        # authenticated upload — see #6685. Results-only flag (no jacoco). 
Staged
+        # on the ubuntu leg only: the integration specs run on both ubuntu and
+        # macOS, so staging both would clash on the `codecov-amber-integration`
+        # artifact name (upload-artifact@v4 rejects duplicates); the ubuntu 
run is
+        # representative for Test Analytics. Skipped on backport builds.
+        if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == 
'' && !cancelled()
+        shell: bash
+        env:
+          CC_COVERAGE: ""
+          CC_RESULTS: "*/target/test-reports/*.xml amber/junit-integration.xml"
+          CC_PR: ${{ github.event.pull_request.number }}
+          CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+          CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name 
}}
+        run: bash .github/scripts/stage-codecov.sh
+      - name: Upload amber-integration Codecov artifact
+        if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == 
'' && !cancelled()
+        uses: actions/upload-artifact@v4
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./**/target/test-reports/*.xml,./amber/junit-integration.xml
-          flags: amber-integration
-          report_type: test_results
-          fail_ci_if_error: false
+          name: codecov-amber-integration
+          path: cc/
+          retention-days: 1
+          if-no-files-found: warn
 
   platform:
     # Per-service build, test, and license check for the non-amber Scala
@@ -768,27 +773,30 @@ jobs:
             check_exit=1
           fi
           exit "$check_exit"
-      - name: Upload ${{ matrix.service }} coverage to Codecov
-        # Per-service flag so each matrix entry has its own Codecov view
-        # rather than being merged into one umbrella `platform` flag.
-        if: always()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./${{ matrix.service 
}}/target/scala-2.13/jacoco/report/jacoco.xml
-          flags: ${{ matrix.service }}
-          fail_ci_if_error: false
-      - name: Upload ${{ matrix.service }} test results to Codecov
-        # Per-service Test Analytics, mirroring the coverage upload flag.
-        if: ${{ !cancelled() }}
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+      - name: Stage ${{ matrix.service }} coverage for deferred Codecov upload
+        # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an
+        # authenticated upload — see #6685. Per-service flag. Skipped on 
backport
+        # builds (artifact-name clash across the target matrix).
+        if: inputs.backport_target_branch == '' && !cancelled()
+        shell: bash
+        env:
+          CC_COVERAGE: "${{ matrix.service 
}}/target/scala-2.13/jacoco/report/jacoco.xml"
+          CC_RESULTS: "${{ matrix.service }}/target/test-reports/*.xml"
+          CC_PR: ${{ github.event.pull_request.number }}
+          CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+          CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name 
}}
+        run: bash .github/scripts/stage-codecov.sh
+      - name: Upload ${{ matrix.service }} Codecov artifact
+        # Adding a service to the matrix above also needs a matching
+        # { flag: <service>, coverage: true } row in codecov-upload.yml, or 
this
+        # artifact is downloaded by nothing and the service's coverage is 
dropped.
+        if: inputs.backport_target_branch == '' && !cancelled()
+        uses: actions/upload-artifact@v4
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./${{ matrix.service }}/target/test-reports/*.xml
-          flags: ${{ matrix.service }}
-          report_type: test_results
-          disable_search: true
-          fail_ci_if_error: false
+          name: codecov-${{ matrix.service }}
+          path: cc/
+          retention-days: 1
+          if-no-files-found: warn
 
   platform-integration:
     # Boot smoke test for the platform services (mirrors amber-integration: an
@@ -1015,28 +1023,27 @@ jobs:
         # test PR comments and flaky-test detection on main.
         run: |
           cd amber && pytest -m "not integration" --cov=src/main/python 
--cov-report=xml --junit-xml=junit.xml -sv
-      - name: Upload pyamber coverage to Codecov
-        if: matrix.python-version == '3.12' && always()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./amber/coverage.xml
-          flags: pyamber
-          fail_ci_if_error: false
-      - name: Upload pyamber test results to Codecov
-        # Test Analytics ingestion. Runs on the same 3.12 leg that uploads
-        # coverage to keep one canonical source per flag. `!cancelled()`
-        # rather than `always()` so we still upload on test failure (the
-        # whole point of Test Analytics) but skip cancelled runs.
-        if: matrix.python-version == '3.12' && !cancelled()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+      - name: Stage pyamber coverage for deferred Codecov upload
+        # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an
+        # authenticated upload — see #6685. 3.12 leg only (the leg that 
produced
+        # coverage.xml / the pip-licenses snapshot). Skipped on backport 
builds.
+        if: matrix.python-version == '3.12' && inputs.backport_target_branch 
== '' && !cancelled()
+        shell: bash
+        env:
+          CC_COVERAGE: amber/coverage.xml
+          CC_RESULTS: amber/junit.xml
+          CC_PR: ${{ github.event.pull_request.number }}
+          CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+          CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name 
}}
+        run: bash .github/scripts/stage-codecov.sh
+      - name: Upload pyamber Codecov artifact
+        if: matrix.python-version == '3.12' && inputs.backport_target_branch 
== '' && !cancelled()
+        uses: actions/upload-artifact@v4
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./amber/junit.xml
-          flags: pyamber
-          report_type: test_results
-          disable_search: true
-          fail_ci_if_error: false
+          name: codecov-pyamber
+          path: cc/
+          retention-days: 1
+          if-no-files-found: warn
 
   agent-service:
     if: ${{ inputs.run_agent_service }}
@@ -1085,26 +1092,29 @@ jobs:
         # Test Analytics upload below can feed Codecov's failing-test PR
         # comments and flaky-test detection on main.
         run: bun test --coverage --coverage-reporter=lcov --reporter=junit 
--reporter-outfile=junit.xml
-      - name: Upload agent-service coverage to Codecov
-        if: matrix.os == 'ubuntu-latest' && always()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./agent-service/coverage/lcov.info
-          flags: agent-service
-          fail_ci_if_error: false
-      - name: Upload agent-service test results to Codecov
-        # Test Analytics ingestion. Runs on the same ubuntu leg that
-        # uploads coverage. `!cancelled()` so test failures still upload.
-        if: matrix.os == 'ubuntu-latest' && !cancelled()
-        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+      - name: Stage agent-service coverage for deferred Codecov upload
+        # Deferred to codecov-upload.yml (workflow_run) so fork PRs get an
+        # authenticated upload — see #6685. ubuntu leg only. working-directory 
is
+        # pinned to the workspace root (this job otherwise defaults to
+        # agent-service/) so the staged cc/ lands where the artifact upload 
expects.
+        if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == 
'' && !cancelled()
+        shell: bash
+        working-directory: ${{ github.workspace }}
+        env:
+          CC_COVERAGE: agent-service/coverage/lcov.info
+          CC_RESULTS: agent-service/junit.xml
+          CC_PR: ${{ github.event.pull_request.number }}
+          CC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+          CC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name 
}}
+        run: bash .github/scripts/stage-codecov.sh
+      - name: Upload agent-service Codecov artifact
+        if: matrix.os == 'ubuntu-latest' && inputs.backport_target_branch == 
'' && !cancelled()
+        uses: actions/upload-artifact@v4
         with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./agent-service/junit.xml
-          flags: agent-service
-          report_type: test_results
-          disable_search: true
-          fail_ci_if_error: false
+          name: codecov-agent-service
+          path: cc/
+          retention-days: 1
+          if-no-files-found: warn
 
   infra:
     # Generic project-tooling job — runs the lightweight (no docker, no
diff --git a/.github/workflows/codecov-upload.yml 
b/.github/workflows/codecov-upload.yml
new file mode 100644
index 0000000000..b43e22906e
--- /dev/null
+++ b/.github/workflows/codecov-upload.yml
@@ -0,0 +1,213 @@
+# 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.
+
+# Deferred, authenticated Codecov upload for coverage produced by "Required 
Checks".
+#
+# Why this exists (reported on #6685): a PR from a fork never receives
+# secrets.CODECOV_TOKEN — the build runs on the `pull_request` event and GitHub
+# withholds secrets from fork runs. An inline upload from the build therefore 
uses
+# Codecov's tokenless path, which is rate-limited; under a burst of fork-PR 
uploads
+# Codecov processes the report but silently drops the PR comment. This 
workflow runs
+# on `workflow_run`, in the base-repo context where the token IS available, and
+# re-uploads the staged coverage/test-results artifacts with authentication so 
the
+# comment posts reliably even in bursts.
+#
+# Each build job stages its reports as a `codecov-<flag>` artifact with a 
uniform
+# layout — coverage/ (coverage reports) and results/ (JUnit XMLs) plus 
pr-number /
+# commit-sha / branch identifier files. The matrix below re-uploads each flag 
with
+# `directory:` scoping so Codecov's search is confined to that flag's files 
(this is
+# what lets the amber flag's ~8 same-basename module jacoco.xml reports upload 
without
+# a fragile explicit file list). Codecov requires one upload per flag (flags 
in a
+# single upload apply to all its files, and carryforward needs the flag at 
upload
+# time), so the flags cannot be collapsed into fewer calls — the win here is 
the
+# token, not fewer uploads.
+#
+# NOTE: `workflow_run` only fires from the copy of this file on the default 
branch, so
+# this has NO effect until merged to main. After merge, exercise it manually 
via the
+# workflow_dispatch entry below, passing a finished "Required Checks" run id.
+
+name: Codecov Upload
+
+on:
+  workflow_run:
+    workflows: ["Required Checks"]
+    types:
+      - completed
+  workflow_dispatch:
+    inputs:
+      run_id:
+        description: "Finished 'Required Checks' run id to pull the codecov-* 
artifacts from"
+        required: true
+        type: string
+
+permissions:
+  contents: read
+  actions: read # download-artifact needs this to read artifacts from another 
run
+
+jobs:
+  upload:
+    # Skip only a cancelled/skipped upstream; still run on failure so partial
+    # coverage is reported (mirrors the build's always()/!cancelled() staging).
+    if: >-
+      github.event_name == 'workflow_dispatch' ||
+      (github.event.workflow_run.conclusion != 'cancelled' &&
+       github.event.workflow_run.conclusion != 'skipped')
+    runs-on: ubuntu-latest
+    # Job-level so the group can key on matrix.flag: one upload pass per source
+    # run + flag, and a re-run of the source supersedes the previous attempt.
+    concurrency:
+      group: codecov-upload-${{ github.event.workflow_run.id || inputs.run_id 
}}-${{ matrix.flag }}
+      cancel-in-progress: false
+    strategy:
+      fail-fast: false
+      matrix:
+        # flag = artifact suffix (codecov-<flag>); coverage=false for the
+        # test-results-only flag (amber-integration has no coverage report).
+        #
+        # KEEP IN SYNC with the coverage-producing jobs in build.yml: one row 
per
+        # codecov-<flag> artifact staged there — frontend, amber, 
amber-integration,
+        # pyamber, agent-service, and every service in build.yml's `platform` 
matrix.
+        # A flag staged in build.yml but missing here is downloaded by nothing 
and
+        # its coverage is silently dropped (the reverse — a row here with no 
artifact
+        # — is safe: the download continue-on-errors and the leg skips).
+        include:
+          - { flag: frontend, coverage: true }
+          - { flag: amber, coverage: true }
+          - { flag: amber-integration, coverage: false }
+          - { flag: pyamber, coverage: true }
+          - { flag: agent-service, coverage: true }
+          - { flag: config-service, coverage: true }
+          - { flag: access-control-service, coverage: true }
+          - { flag: file-service, coverage: true }
+          - { flag: computing-unit-managing-service, coverage: true }
+          - { flag: workflow-compiling-service, coverage: true }
+          - { flag: notebook-migration-service, coverage: true }
+    steps:
+      - name: Resolve source run id
+        id: src
+        shell: bash
+        run: echo "run_id=${{ github.event.workflow_run.id || inputs.run_id 
}}" >> "$GITHUB_OUTPUT"
+      - name: Download staged coverage
+        id: dl
+        continue-on-error: true # absent when this flag's job was label-gated 
out
+        # Layout: stage-codecov.sh roots the artifact at cc/ (coverage/, 
results/,
+        # and the pr-number/commit-sha/branch txt files), so downloading to 
path: cc
+        # restores cc/coverage/**, cc/results/**, and cc/pr-number.txt — the 
paths
+        # the steps below reference. Keep that rooting if the staging script 
changes.
+        uses: actions/download-artifact@v4
+        with:
+          name: codecov-${{ matrix.flag }}
+          path: cc
+          run-id: ${{ steps.src.outputs.run_id }}
+          github-token: ${{ github.token }}
+      - name: Read coverage metadata
+        if: steps.dl.outcome == 'success'
+        id: meta
+        shell: bash
+        # Artifact content comes from a (possibly fork) PR build, so sanitize 
each
+        # value to its expected charset before handing it to the Codecov CLI.
+        run: |
+          echo "pr=$(cat cc/pr-number.txt 2>/dev/null | tr -cd '0-9')" >> 
"$GITHUB_OUTPUT"
+          echo "sha=$(cat cc/commit-sha.txt 2>/dev/null | tr -cd '0-9a-fA-F')" 
>> "$GITHUB_OUTPUT"
+          echo "branch=$(cat cc/branch.txt 2>/dev/null | tr -cd 
'[:alnum:]._/:-')" >> "$GITHUB_OUTPUT"
+      - name: Upload ${{ matrix.flag }} coverage to Codecov
+        # fail_ci_if_error: true so a genuine upload failure fails this leg and
+        # trips the notify-failure job below (Codecov posts its own comment 
only on
+        # success, so a failed coverage upload would otherwise leave the PR 
with no
+        # report and no signal). A label-gated-absent flag is not a failure — 
the
+        # download continue-on-errors and this step is skipped via the guards 
above.
+        if: steps.dl.outcome == 'success' && matrix.coverage && 
hashFiles('cc/coverage/**') != ''
+        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          directory: cc/coverage
+          flags: ${{ matrix.flag }}
+          fail_ci_if_error: true
+          override_commit: ${{ steps.meta.outputs.sha }}
+          override_branch: ${{ steps.meta.outputs.branch }}
+          override_pr: ${{ steps.meta.outputs.pr }}
+      - name: Upload ${{ matrix.flag }} test results to Codecov
+        # fail_ci_if_error: true here too (per review) so a test-results 
upload error
+        # also trips notify-failure instead of being silently dropped. Still 
guarded
+        # on hashFiles, so an absent / label-gated flag simply skips.
+        if: steps.dl.outcome == 'success' && hashFiles('cc/results/**') != ''
+        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f 
# v7.0.0
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          directory: cc/results
+          flags: ${{ matrix.flag }}
+          report_type: test_results
+          fail_ci_if_error: true
+          override_commit: ${{ steps.meta.outputs.sha }}
+          override_branch: ${{ steps.meta.outputs.branch }}
+          override_pr: ${{ steps.meta.outputs.pr }}
+
+  notify-failure:
+    # If any upload above failed (coverage or test-results), leave a comment 
on the
+    # PR so a human can re-dispatch it. Codecov posts its own comment on 
SUCCESS, so
+    # we only cover the failure case (per #6730 review). A delayed / queued 
upload
+    # posts nothing — the absence of a Codecov comment then signals a stuck 
run.
+    needs: upload
+    if: ${{ failure() && github.event_name == 'workflow_run' }}
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      actions: read # download-artifact from the source run
+      pull-requests: write # comment on the PR
+    steps:
+      - name: Download coverage metadata
+        id: dl
+        continue-on-error: true
+        uses: actions/download-artifact@v4
+        with:
+          pattern: codecov-*
+          path: cc-all
+          merge-multiple: true # every flag's artifact carries the same 
pr-number.txt
+          run-id: ${{ github.event.workflow_run.id }}
+          github-token: ${{ github.token }}
+      - name: Resolve PR number
+        id: meta
+        shell: bash
+        run: echo "pr=$(cat cc-all/pr-number.txt 2>/dev/null | tr -cd '0-9')" 
>> "$GITHUB_OUTPUT"
+      - name: Comment upload failure on the PR
+        if: steps.meta.outputs.pr != ''
+        uses: actions/github-script@v9
+        with:
+          github-token: ${{ github.token }}
+          script: |
+            const MARKER = '<!-- codecov-upload-failure -->';
+            const pr = Number('${{ steps.meta.outputs.pr }}');
+            const srcRunId = '${{ github.event.workflow_run.id }}';
+            const runUrl = 
`${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
+            const body = [
+              MARKER,
+              '⚠️ **Codecov upload failed.**',
+              '',
+              `The deferred \`Codecov Upload\` workflow ([run](${runUrl})) 
failed, so the coverage report for this PR may be missing. This does **not** 
block merge.`,
+              '',
+              'To recover: re-run the failed jobs in the run above, or 
manually dispatch the **Codecov Upload** workflow with',
+              `\`run_id\`: \`${srcRunId}\`.`,
+            ].join('\n');
+            const { owner, repo } = context.repo;
+            const comments = await 
github.paginate(github.rest.issues.listComments, {
+              owner, repo, issue_number: pr, per_page: 100,
+            });
+            const prior = comments.find(c => c.body && 
c.body.includes(MARKER));
+            if (prior) {
+              await github.rest.issues.updateComment({ owner, repo, 
comment_id: prior.id, body });
+            } else {
+              await github.rest.issues.createComment({ owner, repo, 
issue_number: pr, body });
+            }


Reply via email to