From 3b32ad707358b79b87533c9ae8a47a3abf7b7d6b Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sun, 26 Jul 2026 15:50:48 -0700 Subject: [PATCH 1/2] fix(e2e): accept canonical controller check URLs Signed-off-by: Apurv Kumaria --- test/pr-e2e-gate-dispatch-recovery.test.ts | 20 ++++++++++++++--- test/pr-e2e-gate-lifecycle.test.ts | 26 ++++++++++++++++------ tools/e2e/pr-e2e-gate.mts | 3 ++- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/test/pr-e2e-gate-dispatch-recovery.test.ts b/test/pr-e2e-gate-dispatch-recovery.test.ts index f7f20f163d..65b256522d 100644 --- a/test/pr-e2e-gate-dispatch-recovery.test.ts +++ b/test/pr-e2e-gate-dispatch-recovery.test.ts @@ -625,7 +625,18 @@ describe("PR E2E dispatch-not-observed recovery", () => { ]); }); - it("accepts an exact child authorization after the PATCH response is lost", async () => { + it.each([ + { + label: "exact child URL", + publishedDetailsUrl: "https://github.com/NVIDIA/NemoClaw/actions/runs/23", + }, + { + label: "canonical check URL", + publishedDetailsUrl: "https://github.com/NVIDIA/NemoClaw/runs/17", + }, + ])("accepts an exact child authorization with $label after the PATCH response is lost", async ({ + publishedDetailsUrl, + }) => { const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-pr-e2e-auth-response-")); const outputPath = path.join(workDir, "github-output"); fs.writeFileSync(outputPath, "", { mode: 0o600 }); @@ -633,7 +644,7 @@ describe("PR E2E dispatch-not-observed recovery", () => { vi.stubEnv("GITHUB_REPOSITORY", "NVIDIA/NemoClaw"); vi.stubEnv("GITHUB_OUTPUT", outputPath); const requests: RecordedGitHubRequest[] = []; - let check = reservedCheck(); + let check: Record = reservedCheck(); vi.spyOn(globalThis, "fetch").mockImplementation( createGitHubFetchRouter( [ @@ -668,6 +679,9 @@ describe("PR E2E dispatch-not-observed recovery", () => { check = { ...check, ...((request.body ?? {}) as Record) }; const title = (request.body as { output?: { title?: string } } | undefined)?.output ?.title; + if (title?.startsWith("Running ")) { + check.details_url = publishedDetailsUrl; + } return title?.startsWith("Running ") ? new Response("{", { status: 200 }) : githubResponse(check); @@ -697,7 +711,7 @@ describe("PR E2E dispatch-not-observed recovery", () => { expect(check).toMatchObject({ status: "in_progress", conclusion: null, - details_url: "https://github.com/NVIDIA/NemoClaw/actions/runs/23", + details_url: publishedDetailsUrl, output: { title: expect.stringMatching(/^Running /u) }, }); expect(requests.filter((request) => request.url.endsWith("/dispatches"))).toHaveLength(1); diff --git a/test/pr-e2e-gate-lifecycle.test.ts b/test/pr-e2e-gate-lifecycle.test.ts index b0e9765d02..fa5b99a063 100644 --- a/test/pr-e2e-gate-lifecycle.test.ts +++ b/test/pr-e2e-gate-lifecycle.test.ts @@ -388,7 +388,7 @@ describe("PR E2E controller lifecycle", () => { } }); - it("cancels the child and closes the check when startup fails after dispatch", async () => { + it("revokes and cancels when a lost PATCH publishes a foreign check URL", async () => { const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-pr-e2e-gate-start-")); const outputPath = path.join(workDir, "github-output"); fs.writeFileSync(outputPath, "", { mode: 0o600 }); @@ -397,6 +397,7 @@ describe("PR E2E controller lifecycle", () => { vi.stubEnv("GITHUB_OUTPUT", outputPath); const requests: RecordedGitHubRequest[] = []; let checkPatches = 0; + let failedRunningUpdate: Record | undefined; vi.spyOn(globalThis, "fetch").mockImplementation( createGitHubFetchRouter( [ @@ -436,8 +437,16 @@ describe("PR E2E controller lifecycle", () => { ), githubFetchRoute( ({ url, method }) => url.endsWith("/check-runs/17") && method === "GET", - () => - githubResponse( + () => { + if (failedRunningUpdate) { + return githubResponse( + exactPrGateCheck({ + ...failedRunningUpdate, + details_url: "https://github.com/NVIDIA/NemoClaw/runs/999", + }), + ); + } + return githubResponse( exactPrGateCheck({ output: { title: "Evaluating PR commit", @@ -445,7 +454,8 @@ describe("PR E2E controller lifecycle", () => { "Validating the PR SHA and selecting deterministic E2E jobs and typed targets.", }, }), - ), + ); + }, ), githubFetchRoute( ({ url, method }) => url.endsWith("/actions/runs/23/cancel") && method === "POST", @@ -455,9 +465,11 @@ describe("PR E2E controller lifecycle", () => { ({ url, method }) => url.endsWith("/check-runs/17") && method === "PATCH", (request) => { checkPatches += 1; - return checkPatches === 2 - ? githubResponse({ message: "simulated update failure" }, 500) - : prGateMutationResponse(request); + if (checkPatches === 2) { + failedRunningUpdate = (request.body ?? {}) as Record; + return githubResponse({ message: "simulated update failure" }, 500); + } + return prGateMutationResponse(request); }, ), ], diff --git a/tools/e2e/pr-e2e-gate.mts b/tools/e2e/pr-e2e-gate.mts index 68acd2f168..c478ed51b0 100755 --- a/tools/e2e/pr-e2e-gate.mts +++ b/tools/e2e/pr-e2e-gate.mts @@ -1380,6 +1380,7 @@ async function updateRunningCheck( }, ): Promise { const childRunUrl = `https://github.com/${context.repository}/actions/runs/${options.childRunId}`; + const canonicalCheckUrl = `https://github.com/${context.repository}/runs/${context.checkRunId}`; const selectionCount = options.jobs.length + options.targets.length; const title = `Running ${selectionCount} E2E ${selectionCount === 1 ? "check" : "checks"}`; const summary = `Risk plan ${options.planHash} selected jobs: ${options.jobs.join(", ") || "none"}; targets: ${options.targets.join(", ") || "none"}. Child run: ${childRunUrl}.`; @@ -1394,7 +1395,7 @@ async function updateRunningCheck( title, summary, }); - if (check.details_url !== childRunUrl) { + if (check.details_url !== childRunUrl && check.details_url !== canonicalCheckUrl) { throw new Error("GitHub did not bind the controller check to the selected child run"); } }; From d4f53a19618b4d810494e2094c6aff6b9f6602cc Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sun, 26 Jul 2026 15:56:12 -0700 Subject: [PATCH 2/2] test(e2e): keep canonical URL fixtures linear Signed-off-by: Apurv Kumaria --- test/pr-e2e-gate-dispatch-recovery.test.ts | 11 +++--- test/pr-e2e-gate-lifecycle.test.ts | 42 +++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/test/pr-e2e-gate-dispatch-recovery.test.ts b/test/pr-e2e-gate-dispatch-recovery.test.ts index 65b256522d..665eac9256 100644 --- a/test/pr-e2e-gate-dispatch-recovery.test.ts +++ b/test/pr-e2e-gate-dispatch-recovery.test.ts @@ -676,12 +676,15 @@ describe("PR E2E dispatch-not-observed recovery", () => { githubFetchRoute( ({ url, method }) => url.endsWith("/check-runs/17") && method === "PATCH", (request) => { - check = { ...check, ...((request.body ?? {}) as Record) }; const title = (request.body as { output?: { title?: string } } | undefined)?.output ?.title; - if (title?.startsWith("Running ")) { - check.details_url = publishedDetailsUrl; - } + check = { + ...check, + ...((request.body ?? {}) as Record), + ...(title?.startsWith("Running ") + ? { details_url: publishedDetailsUrl } + : undefined), + }; return title?.startsWith("Running ") ? new Response("{", { status: 200 }) : githubResponse(check); diff --git a/test/pr-e2e-gate-lifecycle.test.ts b/test/pr-e2e-gate-lifecycle.test.ts index fa5b99a063..e62ce5139e 100644 --- a/test/pr-e2e-gate-lifecycle.test.ts +++ b/test/pr-e2e-gate-lifecycle.test.ts @@ -437,25 +437,21 @@ describe("PR E2E controller lifecycle", () => { ), githubFetchRoute( ({ url, method }) => url.endsWith("/check-runs/17") && method === "GET", - () => { - if (failedRunningUpdate) { - return githubResponse( - exactPrGateCheck({ - ...failedRunningUpdate, - details_url: "https://github.com/NVIDIA/NemoClaw/runs/999", - }), - ); - } - return githubResponse( + () => + githubResponse( exactPrGateCheck({ - output: { - title: "Evaluating PR commit", - summary: - "Validating the PR SHA and selecting deterministic E2E jobs and typed targets.", - }, + ...(failedRunningUpdate ?? { + output: { + title: "Evaluating PR commit", + summary: + "Validating the PR SHA and selecting deterministic E2E jobs and typed targets.", + }, + }), + ...(failedRunningUpdate + ? { details_url: "https://github.com/NVIDIA/NemoClaw/runs/999" } + : undefined), }), - ); - }, + ), ), githubFetchRoute( ({ url, method }) => url.endsWith("/actions/runs/23/cancel") && method === "POST", @@ -465,11 +461,13 @@ describe("PR E2E controller lifecycle", () => { ({ url, method }) => url.endsWith("/check-runs/17") && method === "PATCH", (request) => { checkPatches += 1; - if (checkPatches === 2) { - failedRunningUpdate = (request.body ?? {}) as Record; - return githubResponse({ message: "simulated update failure" }, 500); - } - return prGateMutationResponse(request); + failedRunningUpdate = + checkPatches === 2 + ? ((request.body ?? {}) as Record) + : failedRunningUpdate; + return checkPatches === 2 + ? githubResponse({ message: "simulated update failure" }, 500) + : prGateMutationResponse(request); }, ), ],