Skip to content

Commit 48c732b

Browse files
authored
fix: Don't report check to invalid sha (#515)
* fix: Don't report check to old sha This fixes an issue where a failed git checkout (because of a missing ref, network, or auth issues) would cause a failure to be posted to GitHub for the wrong sha - most often the last successfully built sha. This has caused confusion when one or more checks was reported as passing only to be unexpectedly changed to failing for an unrelated reason. It looks for cases where the git build data doesn't match the current build and returning an empty string for the sha if that's the case. Otherwise the behavior should remain the same. I added a new test to cover this case and I've tested it both with a manual repro in a sandbox environment and in a production environment (we run approximately 100k PR checks through this plugin on an average day) to confirm that it's working as expected when build failures occur before or during checkout. Prior to this fix - a failure during git checkout would report the failure to the previous successful sha: ``` > gh api "repos/$ORG/$REPO/commits/$SHA/check-runs?filter=all" | jq '.check_runs[] | {name, head_sha, status, conclusion, completed_at}' { "name": "gh-checks-plugin-fix-test", "head_sha": "71a1cc8f8b956b11b12036b8687acd7a5fafa868", "status": "completed", "conclusion": "failure", "completed_at": "2026-02-18T15:49:53Z" } { "name": "gh-checks-plugin-fix-test", "head_sha": "71a1cc8f8b956b11b12036b8687acd7a5fafa868", "status": "completed", "conclusion": "success", "completed_at": "2026-02-18T15:48:46Z" } ``` And with this fix, the build failed. But, as expected, it skipped reporting the failure to the wrong sha: ``` > gh api "repos/$ORG/$REPO/commits/$SHA/check-runs?filter=all" | jq '.check_runs[] | {name, head_sha, status, conclusion, completed_at}' { "name": "gh-checks-plugin-fix-test", "head_sha": "5dd2fbbbf2d102e0bb72f1ab76e5c00b8b29cf2e", "status": "completed", "conclusion": "success", "completed_at": "2026-02-18T15:58:32Z" } ``` The main caveat with this is that we use Freestyle jobs, not Pipeline jobs, so I'm relying on the existing test case to ensure it's still working as expected for pipelines. * fix tests I had made the original changes based on an older version of the plugin so it would be compatible with the older version of Jenkins we're still using and I forgot to re-run the tests after I cherry-picked the commit to the latest on `master`. This is now passing when I run tests locally. * Avoid potential null pointer exception to fix spotbugs * mock builddata
1 parent a1bb1dc commit 48c732b

File tree

3 files changed

+50
-0
lines changed

3 files changed

+50
-0
lines changed

src/main/java/io/jenkins/plugins/checks/github/GitSCMChecksContext.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ class GitSCMChecksContext extends GitHubChecksContext {
4848

4949
@Override
5050
public String getHeadSha() {
51+
// When checkout fails, BuildData is either absent from the current run or
52+
// carries a stale build number from a previous build. In both cases,
53+
// GIT_COMMIT from run.getEnvironment() is also unreliable because
54+
// GitSCM.buildEnvironment() walks back through previous builds' BuildData.
55+
BuildData gitBuildData = run.getAction(BuildData.class);
56+
if (gitBuildData == null
57+
|| gitBuildData.lastBuild == null
58+
|| gitBuildData.lastBuild.getBuildNumber() != run.getNumber()) {
59+
return StringUtils.EMPTY;
60+
}
61+
5162
try {
5263
String head = getGitCommitEnvironment();
5364
if (StringUtils.isNotBlank(head)) {

src/test/java/io/jenkins/plugins/checks/github/GitHubChecksPublisherFactoryTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import hudson.model.TaskListener;
77
import hudson.plugins.git.GitSCM;
88
import hudson.plugins.git.UserRemoteConfig;
9+
import hudson.plugins.git.util.Build;
10+
import hudson.plugins.git.util.BuildData;
911
import jenkins.scm.api.SCMHead;
1012
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
1113
import org.jenkinsci.plugins.github_branch_source.GitHubAppCredentials;
@@ -78,6 +80,12 @@ void shouldCreateGitHubChecksPublisherFromRunForProjectWithValidGitSCM() throws
7880
SCMFacade scmFacade = mock(SCMFacade.class);
7981
EnvVars envVars = mock(EnvVars.class);
8082

83+
BuildData buildData = mock(BuildData.class);
84+
Build lastBuild = mock(Build.class);
85+
buildData.lastBuild = lastBuild;
86+
when(lastBuild.getBuildNumber()).thenReturn(1);
87+
when(run.getNumber()).thenReturn(1);
88+
when(run.getAction(BuildData.class)).thenReturn(buildData);
8189
when(run.getParent()).thenReturn(job);
8290
when(run.getEnvironment(TaskListener.NULL)).thenReturn(envVars);
8391
when(envVars.get("GIT_COMMIT")).thenReturn("a1b2c3");

src/test/java/io/jenkins/plugins/checks/github/GitSCMChecksContextITest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,35 @@ void shouldRetrieveContextFromPipeline(JenkinsRule j) throws Exception {
8282
assertThat(gitSCMChecksContext.getCredentialsId()).isEqualTo(CREDENTIALS_ID);
8383
assertThat(gitSCMChecksContext.getHeadSha()).isEqualTo(EXISTING_HASH);
8484
}
85+
86+
/**
87+
* Verifies that when a checkout fails on a subsequent build, {@link GitSCMChecksContext#getHeadSha()}
88+
* returns empty rather than returning the stale SHA from the previous successful build.
89+
* This prevents checks from being posted against the wrong commit when checkout fails
90+
* (e.g. due to network flakiness).
91+
*/
92+
@Test
93+
void shouldReturnEmptyHeadShaWhenCheckoutFails(JenkinsRule j) throws Exception {
94+
FreeStyleProject job = j.createFreeStyleProject();
95+
96+
// First build: successful checkout populates BuildData with the correct SHA
97+
GitSCM scm = new GitSCM(GitSCM.createRepoList(HTTP_URL, CREDENTIALS_ID),
98+
Collections.singletonList(new BranchSpec(EXISTING_HASH)),
99+
null, null, Collections.emptyList());
100+
job.setScm(scm);
101+
Run<?, ?> successfulRun = buildSuccessfully(j, job);
102+
assertThat(new GitSCMChecksContext(successfulRun, URL_NAME).getHeadSha()).isEqualTo(EXISTING_HASH);
103+
104+
// Second build: use a non-existent SHA to simulate checkout failure.
105+
// The Git plugin carries over BuildData from the previous build, but since
106+
// the checkout never completes, lastBuild.hudsonBuildNumber still refers
107+
// to build #1.
108+
job.setScm(new GitSCM(GitSCM.createRepoList(HTTP_URL, CREDENTIALS_ID),
109+
Collections.singletonList(new BranchSpec("0000000000000000000000000000000000000001")),
110+
null, null, Collections.emptyList()));
111+
Run<?, ?> failedRun = j.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0, new Action[0]));
112+
113+
// Must return empty, not the previous build's SHA
114+
assertThat(new GitSCMChecksContext(failedRun, URL_NAME).getHeadSha()).isEmpty();
115+
}
85116
}

0 commit comments

Comments
 (0)