Skip to content

Commit 8dd5b65

Browse files
authored
🐛 Fix GitHub Actions PR commit SHA detection (#182)
## Summary - Fix incorrect commit SHA detection for GitHub Actions `pull_request` events - The CLI now reads `GITHUB_EVENT_PATH` to extract the actual PR head commit SHA instead of using `GITHUB_SHA` (which is a merge commit) - This fixes check runs appearing on wrong commits and baseline detection issues ## Problem For `pull_request` events in GitHub Actions, `GITHUB_SHA` points to a **temporary merge commit**, not the actual head commit of the PR. This caused: - Check runs appearing on non-existent or wrong commits - "Check run not found" errors in GitHub - Incorrect baseline detection (comparing against wrong ancestor) From [GitHub's official docs](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request): > "GITHUB_SHA for this event is the last merge commit of the pull request merge branch. If you want to get the commit ID for the last commit to the head branch of the pull request, use `github.event.pull_request.head.sha` instead." ## Solution The CLI now reads the GitHub Actions event payload from `GITHUB_EVENT_PATH` (a JSON file containing the full webhook payload) and extracts `pull_request.head.sha` for PR events. | Scenario | Before | After | |----------|--------|-------| | `push` to `main` | ✅ Correct SHA | ✅ Correct SHA | | `pull_request` event | ❌ Merge commit SHA | ✅ Head commit SHA | | `VIZZLY_COMMIT_SHA` set | ✅ Uses override | ✅ Uses override | ## Changes - `src/utils/ci-env.js`: - Add `getGitHubEvent()` to read and cache event payload - Update `getCommit()` to extract PR head SHA - Update `getPullRequestHeadSha()` with same fix - Update `getPullRequestBaseSha()` to extract base SHA - Tests updated with comprehensive coverage ## Test plan - [x] All existing tests pass - [x] New tests cover PR event file reading - [x] New tests cover fallback to `GITHUB_SHA` for push events - [x] New tests cover error handling (missing file, invalid JSON)
1 parent 587bf38 commit 8dd5b65

File tree

4 files changed

+303
-11
lines changed

4 files changed

+303
-11
lines changed

src/utils/ci-env.js

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,50 @@
44
* Generic functions to extract git and PR information from any CI provider
55
*/
66

7+
import { readFileSync } from 'node:fs';
8+
9+
// Cache for GitHub Actions event payload to avoid re-reading the file
10+
let _githubEventCache = null;
11+
12+
/**
13+
* Read and parse the GitHub Actions event payload from GITHUB_EVENT_PATH.
14+
*
15+
* GitHub Actions sets GITHUB_EVENT_PATH to a file containing the full webhook
16+
* payload that triggered the workflow. This is essential for pull_request and
17+
* pull_request_target events because GITHUB_SHA points to a merge commit,
18+
* not the actual head commit.
19+
*
20+
* @returns {Object} Parsed event payload or empty object on failure
21+
*/
22+
export function getGitHubEvent() {
23+
if (_githubEventCache !== null) {
24+
return _githubEventCache;
25+
}
26+
27+
let eventPath = process.env.GITHUB_EVENT_PATH;
28+
if (!eventPath) {
29+
_githubEventCache = {};
30+
return _githubEventCache;
31+
}
32+
33+
try {
34+
let content = readFileSync(eventPath, 'utf8');
35+
_githubEventCache = JSON.parse(content);
36+
} catch {
37+
// File doesn't exist or invalid JSON - fail silently with empty object
38+
_githubEventCache = {};
39+
}
40+
41+
return _githubEventCache;
42+
}
43+
44+
/**
45+
* Reset the GitHub event cache. Useful for testing.
46+
*/
47+
export function resetGitHubEventCache() {
48+
_githubEventCache = null;
49+
}
50+
751
/**
852
* Get the branch name from CI environment variables
953
* @returns {string|null} Branch name or null if not available
@@ -31,13 +75,41 @@ export function getBranch() {
3175
}
3276

3377
/**
34-
* Get the commit SHA from CI environment variables
78+
* Get the commit SHA from CI environment variables.
79+
*
80+
* IMPORTANT: For GitHub Actions pull_request events, GITHUB_SHA points to a
81+
* temporary merge commit, NOT the actual head commit of the PR. This function
82+
* reads the event payload from GITHUB_EVENT_PATH to extract the correct SHA.
83+
*
84+
* See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
85+
* "GITHUB_SHA for this event is the last merge commit of the pull request merge branch.
86+
* If you want to get the commit ID for the last commit to the head branch of the
87+
* pull request, use github.event.pull_request.head.sha instead."
88+
*
3589
* @returns {string|null} Commit SHA or null if not available
3690
*/
3791
export function getCommit() {
92+
// Vizzly override always takes priority
93+
if (process.env.VIZZLY_COMMIT_SHA) {
94+
return process.env.VIZZLY_COMMIT_SHA;
95+
}
96+
97+
// GitHub Actions: extract the correct SHA based on event type
98+
if (process.env.GITHUB_ACTIONS) {
99+
let event = getGitHubEvent();
100+
101+
// For pull_request events, use the actual head commit SHA (not the merge commit)
102+
// The event payload contains pull_request.head.sha which is what we want
103+
if (event.pull_request?.head?.sha) {
104+
return event.pull_request.head.sha;
105+
}
106+
107+
// For push events or if event parsing failed, GITHUB_SHA is correct
108+
return process.env.GITHUB_SHA || null;
109+
}
110+
111+
// Other CI providers
38112
return (
39-
process.env.VIZZLY_COMMIT_SHA || // Vizzly override
40-
process.env.GITHUB_SHA || // GitHub Actions
41113
process.env.CI_COMMIT_SHA || // GitLab CI
42114
process.env.CIRCLE_SHA1 || // CircleCI
43115
process.env.TRAVIS_COMMIT || // Travis CI
@@ -145,13 +217,30 @@ export function getPullRequestNumber() {
145217
}
146218

147219
/**
148-
* Get the PR head SHA from CI environment variables
220+
* Get the PR head SHA from CI environment variables.
221+
*
222+
* For GitHub Actions, this reads from the event payload to get the actual
223+
* head commit SHA, not the merge commit that GITHUB_SHA points to.
224+
*
149225
* @returns {string|null} PR head SHA or null if not available
150226
*/
151227
export function getPullRequestHeadSha() {
228+
// Vizzly override always takes priority
229+
if (process.env.VIZZLY_PR_HEAD_SHA) {
230+
return process.env.VIZZLY_PR_HEAD_SHA;
231+
}
232+
233+
// GitHub Actions: extract from event payload for PRs
234+
if (process.env.GITHUB_ACTIONS) {
235+
let event = getGitHubEvent();
236+
if (event.pull_request?.head?.sha) {
237+
return event.pull_request.head.sha;
238+
}
239+
return process.env.GITHUB_SHA || null;
240+
}
241+
242+
// Other CI providers
152243
return (
153-
process.env.VIZZLY_PR_HEAD_SHA || // Vizzly override
154-
process.env.GITHUB_SHA || // GitHub Actions
155244
process.env.CI_COMMIT_SHA || // GitLab CI
156245
process.env.CIRCLE_SHA1 || // CircleCI
157246
process.env.TRAVIS_COMMIT || // Travis CI
@@ -166,12 +255,29 @@ export function getPullRequestHeadSha() {
166255
}
167256

168257
/**
169-
* Get the PR base SHA from CI environment variables
258+
* Get the PR base SHA from CI environment variables.
259+
*
260+
* For GitHub Actions, this reads from the event payload to get the base
261+
* branch SHA that the PR is targeting.
262+
*
170263
* @returns {string|null} PR base SHA or null if not available
171264
*/
172265
export function getPullRequestBaseSha() {
266+
// Vizzly override always takes priority
267+
if (process.env.VIZZLY_PR_BASE_SHA) {
268+
return process.env.VIZZLY_PR_BASE_SHA;
269+
}
270+
271+
// GitHub Actions: extract from event payload
272+
if (process.env.GITHUB_ACTIONS) {
273+
let event = getGitHubEvent();
274+
if (event.pull_request?.base?.sha) {
275+
return event.pull_request.base.sha;
276+
}
277+
}
278+
279+
// Other CI providers
173280
return (
174-
process.env.VIZZLY_PR_BASE_SHA || // Vizzly override
175281
process.env.CI_MERGE_REQUEST_TARGET_BRANCH_SHA || // GitLab CI
176282
null // Most CIs don't provide this
177283
);

0 commit comments

Comments
 (0)