diff --git a/.github/workflows/ci-failure-comment.yml b/.github/workflows/ci-failure-comment.yml new file mode 100644 index 00000000000..500e125012f --- /dev/null +++ b/.github/workflows/ci-failure-comment.yml @@ -0,0 +1,115 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: CI Failure Comment + +# This workflow runs in the context of the base repo (not the fork) and has +# write access to post PR comments. It is triggered when the Linux Build +# workflow completes and picks up failure artifacts uploaded by status jobs. +# +# Security: This workflow never checks out or executes PR code. It only reads +# markdown artifacts generated by our own status jobs and posts them as +# comments via the GitHub API. The artifact content is read as a string and +# passed through the GitHub REST API (not interpolated into shell commands), +# which prevents injection attacks. +# +# zizmor:disable:dangerous-triggers -- only reads artifacts and posts comments, no PR code execution. +on: + workflow_run: + workflows: [Linux Build using GCC] + types: + - completed + +permissions: + pull-requests: write + issues: write + actions: read + +jobs: + post-failure-comment: + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion != 'success' + runs-on: ubuntu-latest + steps: + - name: Get PR number + id: pr + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }} + HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + run: | + pr_number=$(gh api \ + "/repos/${REPO}/pulls?head=${HEAD_OWNER}:${HEAD_BRANCH}&state=open" \ + -q '.[0].number // empty') + + if [ -z "$pr_number" ]; then + echo "No open PR found for branch ${HEAD_BRANCH}" + exit 0 + fi + echo "number=$pr_number" >> "$GITHUB_OUTPUT" + + - name: Download failure artifacts + if: steps.pr.outputs.number + uses: actions/download-artifact@v4 + with: + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + pattern: ci-failure-* + path: /tmp/ci-failures + merge-multiple: false + + - name: Post failure comments on PR + if: steps.pr.outputs.number + uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ steps.pr.outputs.number }} + with: + script: | + const fs = require('fs'); + const path = require('path'); + const prNumber = parseInt(process.env.PR_NUMBER, 10); + const failuresDir = '/tmp/ci-failures'; + + if (!fs.existsSync(failuresDir)) { + console.log('No failure artifacts found.'); + return; + } + + // Collect all comment.md files from artifact subdirectories + const comments = []; + for (const entry of fs.readdirSync(failuresDir)) { + const commentPath = path.join(failuresDir, entry, 'comment.md'); + if (fs.existsSync(commentPath)) { + comments.push(fs.readFileSync(commentPath, 'utf8').trim()); + } + } + + if (comments.length === 0) { + console.log('No failure comment files found in artifacts.'); + return; + } + + // Combine all failure comments into a single PR comment + const body = comments.join('\n\n---\n\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: body, + }); + + console.log(`Posted failure comment on PR #${prNumber}`); diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 9a23b3ba50c..f91a404668d 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -45,7 +45,6 @@ on: permissions: contents: read - pull-requests: write concurrency: group: ${{ github.workflow }}-${{ github.repository }}-${{ github.head_ref || github.sha }} diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 4298bb82e1a..a3dc69a0b9f 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -18,3 +18,4 @@ rules: dangerous-triggers: ignore: - build-impact-comment.yml + - ci-failure-comment.yml