Skip to content

Commit d151e27

Browse files
committed
Produce the comment text within the main PR CI workflow to ensure comment and CI always match
1 parent c2f118d commit d151e27

7 files changed

Lines changed: 138 additions & 249 deletions

File tree

.github/scripts/build-pr-classification-comment.sc

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313

1414
// Builds the Markdown body for the PR sticky comment that summarizes how the
1515
// `changes` job classified the diff and which suite groups will run / be
16-
// skipped.
16+
// skipped. Intended to be invoked from `ci.yml`'s `changes` job so the
17+
// resulting comment reflects the exact classification CI is using; the
18+
// `pr-classify-comment.yml` workflow then downloads the rendered body as
19+
// an artifact and posts it as a sticky PR comment.
1720
//
1821
// Inputs (env vars):
1922
// CLASSIFY_OUTPUT_FILE - KEY=VALUE file produced by classify-changes.sc.
2023
// OVERRIDE_OUTPUT_FILE - KEY=VALUE file produced by check-override-keywords.sc.
2124
// COMMENT_OUTPUT_FILE - Path to write the rendered Markdown to (default: comment.md).
22-
// CLASSIFY_RUN_ID - Run ID of the classification workflow (optional).
23-
// CLASSIFY_RUN_URL - URL to the classification workflow run (optional).
24-
// CI_RUN_ID - Run ID of the matching CI workflow run (optional).
25-
// CI_RUN_URL - URL to the matching CI workflow run, or a fallback.
25+
// CI_RUN_ID - Run ID of the CI workflow run (optional).
26+
// CI_RUN_URL - URL to the CI workflow run (optional).
2627

2728
import prclassify.*
2829

@@ -39,8 +40,6 @@ def overrideContributions(signals: Signals): Seq[(OverrideKey, Seq[SuiteGroup])]
3940

4041
def renderComment(
4142
signals: Signals,
42-
classifyRunId: Option[String],
43-
classifyRunUrl: Option[String],
4443
ciRunId: Option[String],
4544
ciRunUrl: Option[String]
4645
): String =
@@ -87,17 +86,8 @@ def renderComment(
8786
case Some(id) => Some(s"Full CI run: [#$id](${ciRunUrl.getOrElse("")})")
8887
case None => ciRunUrl.map(url => s"Full CI run: $url")
8988

90-
val classifySection: Option[String] = classifyRunId.map: id =>
91-
s"_Classified in run [#$id](${classifyRunUrl.getOrElse("")})._"
92-
9389
val sections =
94-
Seq(
95-
Some(headerSection),
96-
Some(suitesSection),
97-
overridesSection,
98-
ciRunSection,
99-
classifySection
100-
).flatten
90+
Seq(Some(headerSection), Some(suitesSection), overridesSection, ciRunSection).flatten
10191

10292
sections.mkString("\n\n") + "\n"
10393

@@ -111,11 +101,15 @@ val commentPath = Env.toAbsolutePath(
111101

112102
val body = renderComment(
113103
signals,
114-
classifyRunId = Env.opt(EnvNames.ClassifyRunId),
115-
classifyRunUrl = Env.opt(EnvNames.ClassifyRunUrl),
116104
ciRunId = Env.opt(EnvNames.CiRunId),
117105
ciRunUrl = Env.opt(EnvNames.CiRunUrl)
118106
)
107+
println(
108+
"""Generated comment body:
109+
|-----------------------
110+
|$body
111+
|-----------------------""".stripMargin
112+
)
119113

120114
os.write.over(commentPath, body, createFolders = true)
121115
println(s"Wrote comment to $commentPath")

.github/scripts/fetch-pr-changed-files.sc

Lines changed: 0 additions & 62 deletions
This file was deleted.

.github/scripts/pr-classify-lib/EnvNames.scala

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package prclassify
55
*/
66
object EnvNames:
77

8-
// ---- Inputs ----
8+
// ---- Inputs to classify-changes.sc / check-override-keywords.sc ----
99
/** "pull_request", "push", ... */
1010
val EventName = "EVENT_NAME"
1111

@@ -20,15 +20,7 @@ object EnvNames:
2020
/** Body of the pull request (scanned for override markers). */
2121
val PrBody = "PR_BODY"
2222

23-
/** "owner/repo". */
24-
val Repo = "REPO"
25-
val PrNumber = "PR_NUMBER"
26-
val HeadSha = "HEAD_SHA"
27-
28-
/** e.g. "https://github.com". */
29-
val ServerUrl = "SERVER_URL"
30-
31-
// ---- Outputs (file-based) ----
23+
// ---- KEY=VALUE / comment artifact paths ----
3224
/** Path where `classify-changes` writes its KEY=VALUE output. */
3325
val ClassifyOutputFile = "CLASSIFY_OUTPUT_FILE"
3426

@@ -39,22 +31,9 @@ object EnvNames:
3931
*/
4032
val CommentOutputFile = "COMMENT_OUTPUT_FILE"
4133

42-
// ---- Run-link context ----
43-
val ClassifyRunId = "CLASSIFY_RUN_ID"
44-
val ClassifyRunUrl = "CLASSIFY_RUN_URL"
45-
val CiRunId = "CI_RUN_ID"
46-
val CiRunUrl = "CI_RUN_URL"
47-
48-
// ---- Script tunables ----
49-
/** Name of the workflow to search for when resolving the CI run link. */
50-
val WorkflowName = "WORKFLOW_NAME"
51-
val MaxAttempts = "MAX_ATTEMPTS"
52-
val RetryDelayMs = "RETRY_DELAY_MS"
53-
val RunIdOutput = "RUN_ID_OUTPUT"
54-
val RunUrlOutput = "RUN_URL_OUTPUT"
55-
56-
/** Name of the GITHUB_OUTPUT key `fetch-pr-changed-files` writes to. */
57-
val OutputName = "OUTPUT_NAME"
34+
// ---- CI run context (embedded in the rendered comment) ----
35+
val CiRunId = "CI_RUN_ID"
36+
val CiRunUrl = "CI_RUN_URL"
5837

5938
// ---- GitHub Actions built-ins ----
6039
val GitHubOutput = "GITHUB_OUTPUT"

.github/scripts/resolve-ci-run-link.sc

Lines changed: 0 additions & 91 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
# Polls the GitHub REST API until the `changes` job of the CI workflow run
3+
# matching $HEAD_SHA has finished. Exits 0 on success (and writes the run id
4+
# to $GITHUB_OUTPUT as `run_id`), non-zero on failure or timeout.
5+
#
6+
# Expected environment variables:
7+
# GH_TOKEN - token used by `gh api`.
8+
# REPO - "owner/name" of the repository.
9+
# HEAD_SHA - PR head SHA to match CI runs against.
10+
# GITHUB_OUTPUT - standard GitHub Actions output file.
11+
# MAX_ATTEMPTS - (optional, default 40) number of polling attempts.
12+
# INTERVAL_SECONDS - (optional, default 15) seconds between attempts.
13+
# CI_WORKFLOW_NAME - (optional, default "CI") name of the workflow to wait for.
14+
# CI_JOB_NAME - (optional, default "changes") name of the job to wait for.
15+
set -euo pipefail
16+
17+
: "${GH_TOKEN:?GH_TOKEN is required}"
18+
: "${REPO:?REPO is required}"
19+
: "${HEAD_SHA:?HEAD_SHA is required}"
20+
: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}"
21+
22+
max_attempts="${MAX_ATTEMPTS:-40}"
23+
interval="${INTERVAL_SECONDS:-15}"
24+
workflow_name="${CI_WORKFLOW_NAME:-CI}"
25+
job_name="${CI_JOB_NAME:-changes}"
26+
27+
attempt=0
28+
while [ "$attempt" -lt "$max_attempts" ]; do
29+
attempt=$((attempt + 1))
30+
31+
# Find the most recent matching workflow run for this head SHA. The run
32+
# may not be discoverable for a few seconds after the PR event dispatches.
33+
run_id=$(gh api \
34+
"repos/$REPO/actions/runs?event=pull_request&head_sha=$HEAD_SHA&per_page=30" \
35+
--jq "[.workflow_runs[] | select(.name==\"$workflow_name\")] | sort_by(.run_number) | last | .id // empty")
36+
37+
if [ -z "$run_id" ]; then
38+
echo "$workflow_name run not yet discoverable for $HEAD_SHA (attempt $attempt/$max_attempts)"
39+
sleep "$interval"
40+
continue
41+
fi
42+
43+
job=$(gh api "repos/$REPO/actions/runs/$run_id/jobs" \
44+
--jq ".jobs[] | select(.name==\"$job_name\") | {status: .status, conclusion: .conclusion}")
45+
status=$(echo "$job" | jq -r '.status // empty')
46+
conclusion=$(echo "$job" | jq -r '.conclusion // empty')
47+
48+
if [ "$status" = "completed" ]; then
49+
if [ "$conclusion" = "success" ]; then
50+
echo "$job_name job completed in $workflow_name run $run_id"
51+
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
52+
exit 0
53+
fi
54+
echo "::error::$job_name job in $workflow_name run $run_id finished with conclusion=$conclusion"
55+
exit 1
56+
fi
57+
58+
echo "$workflow_name run $run_id, $job_name job status=$status (attempt $attempt/$max_attempts)"
59+
sleep "$interval"
60+
done
61+
62+
echo "::error::Timed out after $max_attempts attempts waiting for the $job_name job in $workflow_name"
63+
exit 1

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,35 @@ jobs:
4242
env:
4343
EVENT_NAME: ${{ github.event_name }}
4444
BASE_REF: ${{ github.event.pull_request.base.ref }}
45+
CLASSIFY_OUTPUT_FILE: ${{ runner.temp }}/classify.env
4546
run: .github/scripts/classify-changes.sc
4647
- name: Check override keywords
4748
id: overrides
4849
env:
4950
EVENT_NAME: ${{ github.event_name }}
5051
PR_BODY: ${{ github.event.pull_request.body }}
52+
OVERRIDE_OUTPUT_FILE: ${{ runner.temp }}/overrides.env
5153
run: .github/scripts/check-override-keywords.sc
54+
# Build the sticky-comment body inside the CI run itself so the comment
55+
# posted by pr-classify-comment.yml reflects the exact classification
56+
# this CI run is using. The artifact is consumed by pr-classify-comment.yml.
57+
- name: Build PR classification comment
58+
if: github.event_name == 'pull_request'
59+
env:
60+
CLASSIFY_OUTPUT_FILE: ${{ runner.temp }}/classify.env
61+
OVERRIDE_OUTPUT_FILE: ${{ runner.temp }}/overrides.env
62+
COMMENT_OUTPUT_FILE: ${{ runner.temp }}/pr-classification-comment.md
63+
CI_RUN_ID: ${{ github.run_id }}
64+
CI_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
65+
run: .github/scripts/build-pr-classification-comment.sc
66+
- name: Upload PR classification comment artifact
67+
if: github.event_name == 'pull_request'
68+
uses: actions/upload-artifact@v7
69+
with:
70+
name: pr-classification-comment
71+
path: ${{ runner.temp }}/pr-classification-comment.md
72+
if-no-files-found: error
73+
retention-days: 1
5274

5375
unit-tests:
5476
needs: [changes]

0 commit comments

Comments
 (0)