Skip to content

clang-tidy-bazel-post #123

clang-tidy-bazel-post

clang-tidy-bazel-post #123

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
id: event
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's `github-pr-review` reporter reads GITHUB_EVENT_PATH
# expecting a pull_request payload. The real event here is
# workflow_run, so we synthesize the minimum payload reviewdog
# needs and point GITHUB_EVENT_PATH at it for the next step.
EVENT_PATH="${RUNNER_TEMP}/pr-event.json"
python3 - <<'PY' > "$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
echo "event_path=${EVENT_PATH}" >> "$GITHUB_OUTPUT"
- name: Set up reviewdog
uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
with:
reviewdog_version: latest
- name: Dump reviewdog-visible env and event payload
# Gated on debug re-runs. Reviewdog emits "this is not PullRequest
# build" and posts nothing for fork PRs even though the synthesized
# pull_request payload is well-formed (verified locally against
# reviewdog v0.21.0). Suspect: step-level `env:` cannot override
# runner-protected GITHUB_EVENT_PATH / GITHUB_EVENT_NAME, so reviewdog
# reads the runner's workflow_run event payload instead of our synth.
# This step proves what reviewdog actually sees on the next debug
# re-run.
if: runner.debug == '1'
env:
GITHUB_EVENT_NAME: pull_request
GITHUB_EVENT_PATH: ${{ steps.event.outputs.event_path }}
GITHUB_SHA: ${{ steps.meta.outputs.head_sha }}
GITHUB_REPOSITORY: ${{ steps.meta.outputs.base_repo }}
run: |
echo "::group::GITHUB_* env visible to next step"
env | grep -E '^(GITHUB_|RUNNER_|REVIEWDOG_)' | sort
echo "::endgroup::"
echo "::group::Synth event file location and content"
echo "steps.event.outputs.event_path = ${{ steps.event.outputs.event_path }}"
echo "Effective GITHUB_EVENT_PATH = ${GITHUB_EVENT_PATH}"
if [ -f "${GITHUB_EVENT_PATH}" ]; then
echo "--- file exists, content: ---"
cat "${GITHUB_EVENT_PATH}"
else
echo "::warning::GITHUB_EVENT_PATH file does not exist"
fi
echo "::endgroup::"
echo "::group::Synth file at literal path (should match above)"
SYNTH="${{ steps.event.outputs.event_path }}"
[ -f "${SYNTH}" ] && cat "${SYNTH}" || echo "missing"
echo "::endgroup::"
- name: Run reviewdog
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT_NAME: pull_request
GITHUB_EVENT_PATH: ${{ steps.event.outputs.event_path }}
GITHUB_SHA: ${{ steps.meta.outputs.head_sha }}
GITHUB_REPOSITORY: ${{ steps.meta.outputs.base_repo }}
run: |
# On debug re-runs add reviewdog's own -log-level=debug so the
# cienv detection trace is visible alongside the env dump above.
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