Skip to content

clang-tidy-bazel-post #1240

clang-tidy-bazel-post

clang-tidy-bazel-post #1240

name: clang-tidy-bazel-post
# Runs in the base repository's context with a writable GITHUB_TOKEN, so it
# can post reviewdog comments on PRs opened from forks (which the upstream
# `clang-tidy-bazel` workflow cannot, since fork pull_request runs get a
# read-only token by GitHub's design).
#
# Security: this workflow MUST NOT execute untrusted PR code. It only reads
# the text artifact (clang-tidy.txt) produced by the upstream workflow and
# the metadata file we wrote there ourselves. No checkout of the PR head,
# no bazel build, no scripts from the fork.
on:
workflow_run:
workflows: ["clang-tidy-bazel"]
types:
- completed
permissions:
contents: read
pull-requests: write
# Needed for actions/download-artifact@v4 to fetch from another workflow.
actions: read
jobs:
Post-Reviewdog:
# Run on success AND failure: Stage A intentionally exits non-zero when
# clang-tidy findings exist on the PR diff (to fail the required PR
# check), but the artifact is uploaded *before* that fail step, so the
# findings are still posted as review comments. Only skip on cancelled.
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
runs-on: ${{ vars.USE_SELF_HOSTED == 'true' && 'self-hosted' || 'ubuntu-latest' }}
steps:
# Reviewdog's github-pr-review reporter resolves the local git root
# before flushing comments and silently no-ops if .git is missing.
# Check out the base repo (default branch, shallow) so reviewdog has
# a .git directory to operate against. No fork code involved — this
# is the base repo at HEAD of its default branch.
- name: Check out base repo for reviewdog .git requirement
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
# Default ref in workflow_run context is the base repo's default
# branch, which is the safe choice here. Reviewdog uses GitHub
# API to fetch the actual PR diff, so the local SHA need not
# match the PR head.
- name: Download clang-tidy artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: clang-tidy-bazel
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Load PR metadata
id: meta
run: |
# pr-meta.txt is produced by the upstream workflow from the
# pull_request event payload. It is text we wrote ourselves —
# not arbitrary fork content — and is parsed with a strict
# allowlist below before being exported.
if [ ! -f pr-meta.txt ]; then
echo "::error::pr-meta.txt missing from artifact"
exit 1
fi
while IFS='=' read -r key value; do
case "$key" in
pr_number|head_sha|base_sha|head_repo|base_repo) ;;
*) continue ;;
esac
# Validate values: numbers, hex SHAs, or owner/repo slugs only.
case "$key" in
pr_number)
[[ "$value" =~ ^[0-9]+$ ]] || { echo "::error::bad pr_number"; exit 1; } ;;
head_sha|base_sha)
[[ "$value" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::bad $key"; exit 1; } ;;
head_repo|base_repo)
[[ "$value" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "::error::bad $key"; exit 1; } ;;
esac
echo "$key=$value" >> "$GITHUB_OUTPUT"
done < pr-meta.txt
- name: Synthesize pull_request event payload
env:
PR_NUMBER: ${{ steps.meta.outputs.pr_number }}
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
BASE_SHA: ${{ steps.meta.outputs.base_sha }}
HEAD_REPO: ${{ steps.meta.outputs.head_repo }}
BASE_REPO: ${{ steps.meta.outputs.base_repo }}
run: |
# reviewdog reads pull_request.number from the event file at
# GITHUB_EVENT_PATH; the workflow_run event has none (its
# pull_requests[] is empty for fork PRs), so it bails with "this is
# not PullRequest build". GITHUB_EVENT_PATH is runner-protected, so
# an env: override is silently ignored — overwrite the event file in
# place instead. Safe: nothing downstream reads the original event.
python3 - <<'PY' > "${GITHUB_EVENT_PATH}"
import json, os
payload = {
"action": "synchronize",
"number": int(os.environ["PR_NUMBER"]),
"pull_request": {
"number": int(os.environ["PR_NUMBER"]),
"head": {
"sha": os.environ["HEAD_SHA"],
"repo": {"full_name": os.environ["HEAD_REPO"]},
},
"base": {
"sha": os.environ["BASE_SHA"],
"repo": {"full_name": os.environ["BASE_REPO"]},
},
},
"repository": {
"full_name": os.environ["BASE_REPO"],
"owner": {"login": os.environ["BASE_REPO"].split("/")[0]},
"name": os.environ["BASE_REPO"].split("/")[1],
},
}
print(json.dumps(payload))
PY
- name: Set up reviewdog
uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
with:
reviewdog_version: latest
- name: Dump reviewdog-visible env and event payload
# Debug-gated: confirm the event file now holds the synthesized
# pull_request payload reviewdog will read.
if: runner.debug == '1'
run: |
echo "::group::GITHUB_* env visible to reviewdog"
env | grep -E '^(GITHUB_|RUNNER_|REVIEWDOG_)' | sort
echo "::endgroup::"
echo "::group::Effective GITHUB_EVENT_PATH content (should be our synth)"
echo "Effective GITHUB_EVENT_PATH = ${GITHUB_EVENT_PATH}"
if [ -f "${GITHUB_EVENT_PATH}" ]; then
cat "${GITHUB_EVENT_PATH}"
else
echo "::warning::GITHUB_EVENT_PATH file does not exist"
fi
echo "::endgroup::"
- name: Run reviewdog
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# -log-level=debug on debug re-runs for tracing.
LOG_FLAGS=()
if [ "${RUNNER_DEBUG}" = "1" ]; then
LOG_FLAGS+=(-log-level=debug)
fi
reviewdog \
"${LOG_FLAGS[@]}" \
-efm="%E%f:%l:%c: error: %m" \
-efm="%W%f:%l:%c: warning: %m" \
-name="clang-tidy" \
-reporter=github-pr-review \
-filter-mode=added \
-fail-level=any \
< clang-tidy.txt