From 487269aea0945be97161c43706692bb03bc2fe2a Mon Sep 17 00:00:00 2001 From: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com> Date: Fri, 15 May 2026 05:15:59 +0530 Subject: [PATCH] fix(scm-github): fetch in-progress failed job logs --- packages/plugins/scm-github/src/index.ts | 33 ++++++++++++++----- .../plugins/scm-github/test/index.test.ts | 28 ++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/packages/plugins/scm-github/src/index.ts b/packages/plugins/scm-github/src/index.ts index 78ebd4e9e..8d065db01 100644 --- a/packages/plugins/scm-github/src/index.ts +++ b/packages/plugins/scm-github/src/index.ts @@ -221,6 +221,29 @@ function extractFailedStep(log: string): string | undefined { return lastStep; } +async function getFailedJobLog( + pr: PRInfo, + runReference: { runId: string; jobId?: string }, +): Promise { + try { + return await gh([ + "run", + "view", + runReference.runId, + "--repo", + repoFlag(pr), + "--log-failed", + ...(runReference.jobId ? ["--job", runReference.jobId] : []), + ]); + } catch (err) { + if (!runReference.jobId) throw err; + return gh([ + "api", + `repos/${pr.owner}/${pr.repo}/actions/jobs/${runReference.jobId}/logs`, + ]); + } +} + async function getCIChecksFromStatusRollup(pr: PRInfo): Promise { const raw = await gh([ "pr", @@ -886,15 +909,7 @@ function createGitHubSCM(): SCM { if (seenRuns.has(seenKey)) continue; seenRuns.add(seenKey); - const log = await gh([ - "run", - "view", - runReference.runId, - "--repo", - repoFlag(pr), - "--log-failed", - ...(runReference.jobId ? ["--job", runReference.jobId] : []), - ]); + const log = await getFailedJobLog(pr, runReference); const failedJob: CIFailureSummary["failedJobs"][number] = { name: check.name, diff --git a/packages/plugins/scm-github/test/index.test.ts b/packages/plugins/scm-github/test/index.test.ts index c9e90bcd0..501ff4e09 100644 --- a/packages/plugins/scm-github/test/index.test.ts +++ b/packages/plugins/scm-github/test/index.test.ts @@ -707,6 +707,34 @@ describe("scm-github plugin", () => { await expect(scm.getCIFailureSummary?.(pr, checks)).resolves.toBeNull(); }); + + it("falls back to job logs API when gh run view cannot read logs yet", async () => { + const checks = [ + { + name: "lint", + status: "failed" as const, + conclusion: "FAILURE", + url: "https://github.com/acme/repo/actions/runs/123/job/456", + }, + ]; + mockGhError("run 123 is still in progress; logs will be available when it is complete"); + mockGhRaw("2026-05-14T23:40:42Z ##[error]Lint failed\nunused variable"); + + const summary = await scm.getCIFailureSummary?.(pr, checks); + + expect(summary?.failedJobs).toEqual([ + { + name: "lint", + runUrl: "https://github.com/acme/repo/actions/runs/123/job/456", + logTail: "2026-05-14T23:40:42Z ##[error]Lint failed\nunused variable", + }, + ]); + expect(ghMock).toHaveBeenLastCalledWith( + expect.stringMatching(/(?:^|[\\/])gh(?:\.(?:exe|cmd|bat))?$/i), + ["api", "repos/acme/repo/actions/jobs/456/logs"], + expect.any(Object), + ); + }); }); // ---- getCISummary ------------------------------------------------------