From 9c05558e747f4160ab0355147dbf3b7f58e95784 Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Mon, 18 May 2026 10:53:56 -0700 Subject: [PATCH 1/3] autoLabelBot: don't re-add triage review if already triaged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a "high priority" or "critical" label is applied, the bot adds "triage review". Previously, if a triager removed "triage review" and then the priority label was later removed and re-applied, the bot would re-add "triage review" — re-triaging an already-triaged issue. Check the issue's events for a prior `unlabeled` event for "triage review" and skip the auto-add in that case. The existing comment at the call site already documented this as the intended behavior; this commit makes it match. --- torchci/lib/bot/autoLabelBot.ts | 22 +++++++++++++++++++++- torchci/test/autoLabelBot.test.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/torchci/lib/bot/autoLabelBot.ts b/torchci/lib/bot/autoLabelBot.ts index 5623ae96e2..2cb2f42ad8 100644 --- a/torchci/lib/bot/autoLabelBot.ts +++ b/torchci/lib/bot/autoLabelBot.ts @@ -357,6 +357,22 @@ function getReleaseNotesCategoryAndTopic( return ["uncategorized", topic]; } +export async function wasLabelPreviouslyRemoved( + context: Context, + labelName: string +): Promise { + const events = await context.octokit.paginate( + context.octokit.issues.listEvents, + context.repo({ + issue_number: context.payload.issue.number, + per_page: 100, + }) + ); + return events.some( + (e: any) => e.event === "unlabeled" && e.label?.name === labelName + ); +} + export async function addNewLabels( existingLabels: string[], labelsToAdd: string[], @@ -412,7 +428,11 @@ function myBot(app: Probot): void { switch (addedLabel) { case "high priority": case "critical": - newLabels.push("triage review"); + // Don't re-add triage review if a human already triaged the + // issue (i.e. previously removed the triage review label). + if (!(await wasLabelPreviouslyRemoved(context, "triage review"))) { + newLabels.push("triage review"); + } break; } diff --git a/torchci/test/autoLabelBot.test.ts b/torchci/test/autoLabelBot.test.ts index 2c53f75dc8..efe10d629d 100644 --- a/torchci/test/autoLabelBot.test.ts +++ b/torchci/test/autoLabelBot.test.ts @@ -62,6 +62,10 @@ describe("auto-label-bot", () => { emptyMockConfig(payload.repository.full_name); const scope = nock("https://api.github.com") + .get( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" + ) + .reply(200, []) .post( "/repos/ezyang/testing-ideal-computing-machine/issues/5/labels", (body) => { @@ -76,6 +80,33 @@ describe("auto-label-bot", () => { scope.done(); }); + test("do not re-add triage review if it was previously removed", async () => { + nock("https://api.github.com") + .post("/app/installations/2/access_tokens") + .reply(200, { token: "test" }); + + const payload = requireDeepCopy("./fixtures/issues.labeled"); + payload["label"] = { name: "high priority" }; + payload["issue"]["labels"] = [{ name: "high priority" }]; + emptyMockConfig(payload.repository.full_name); + + // Issue events show that triage review was previously removed (i.e. + // the issue was already triaged). + const scope = nock("https://api.github.com") + .get( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" + ) + .reply(200, [ + { event: "labeled", label: { name: "high priority" } }, + { event: "labeled", label: { name: "triage review" } }, + { event: "unlabeled", label: { name: "triage review" } }, + ]); + + await probot.receive({ name: "issues", payload, id: "2" }); + + scope.done(); + }); + test("add rocm label when issue title contains ROCm", async () => { nock("https://api.github.com") .post("/app/installations/2/access_tokens") From 509568778837e0103fb6d2e1fead4d2dc22ac98f Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Mon, 18 May 2026 10:56:10 -0700 Subject: [PATCH 2/3] Only skip re-add when triage review was removed in the last hour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If someone applies hi-pri to an old, previously-triaged issue because it resurfaced, we still want the bot to re-request triage. So instead of suppressing the re-add forever, only suppress it when the unlabel event happened within the last hour — which is the window that catches an active triager finishing up. --- torchci/lib/bot/autoLabelBot.ts | 25 +++++++++--- torchci/test/autoLabelBot.test.ts | 66 ++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/torchci/lib/bot/autoLabelBot.ts b/torchci/lib/bot/autoLabelBot.ts index 2cb2f42ad8..9a1769d2e0 100644 --- a/torchci/lib/bot/autoLabelBot.ts +++ b/torchci/lib/bot/autoLabelBot.ts @@ -357,9 +357,10 @@ function getReleaseNotesCategoryAndTopic( return ["uncategorized", topic]; } -export async function wasLabelPreviouslyRemoved( +export async function wasLabelRecentlyRemoved( context: Context, - labelName: string + labelName: string, + withinMs: number ): Promise { const events = await context.octokit.paginate( context.octokit.issues.listEvents, @@ -368,8 +369,12 @@ export async function wasLabelPreviouslyRemoved( per_page: 100, }) ); + const cutoff = Date.now() - withinMs; return events.some( - (e: any) => e.event === "unlabeled" && e.label?.name === labelName + (e: any) => + e.event === "unlabeled" && + e.label?.name === labelName && + new Date(e.created_at).getTime() >= cutoff ); } @@ -428,9 +433,17 @@ function myBot(app: Probot): void { switch (addedLabel) { case "high priority": case "critical": - // Don't re-add triage review if a human already triaged the - // issue (i.e. previously removed the triage review label). - if (!(await wasLabelPreviouslyRemoved(context, "triage review"))) { + // Don't re-add triage review if a human just triaged the issue + // (removed triage review within the last hour). Older removals + // are ignored so that resurfacing an old issue with hi-pri does + // re-request triage. + if ( + !(await wasLabelRecentlyRemoved( + context, + "triage review", + 60 * 60 * 1000 + )) + ) { newLabels.push("triage review"); } break; diff --git a/torchci/test/autoLabelBot.test.ts b/torchci/test/autoLabelBot.test.ts index efe10d629d..d88ed2199d 100644 --- a/torchci/test/autoLabelBot.test.ts +++ b/torchci/test/autoLabelBot.test.ts @@ -80,7 +80,7 @@ describe("auto-label-bot", () => { scope.done(); }); - test("do not re-add triage review if it was previously removed", async () => { + test("do not re-add triage review if it was just removed (within the hour)", async () => { nock("https://api.github.com") .post("/app/installations/2/access_tokens") .reply(200, { token: "test" }); @@ -90,16 +90,27 @@ describe("auto-label-bot", () => { payload["issue"]["labels"] = [{ name: "high priority" }]; emptyMockConfig(payload.repository.full_name); - // Issue events show that triage review was previously removed (i.e. - // the issue was already triaged). + const recent = new Date(Date.now() - 5 * 60 * 1000).toISOString(); const scope = nock("https://api.github.com") .get( "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" ) .reply(200, [ - { event: "labeled", label: { name: "high priority" } }, - { event: "labeled", label: { name: "triage review" } }, - { event: "unlabeled", label: { name: "triage review" } }, + { + event: "labeled", + label: { name: "high priority" }, + created_at: recent, + }, + { + event: "labeled", + label: { name: "triage review" }, + created_at: recent, + }, + { + event: "unlabeled", + label: { name: "triage review" }, + created_at: recent, + }, ]); await probot.receive({ name: "issues", payload, id: "2" }); @@ -107,6 +118,49 @@ describe("auto-label-bot", () => { scope.done(); }); + test("re-add triage review if old issue resurfaces (stale removal)", async () => { + nock("https://api.github.com") + .post("/app/installations/2/access_tokens") + .reply(200, { token: "test" }); + + const payload = requireDeepCopy("./fixtures/issues.labeled"); + payload["label"] = { name: "high priority" }; + payload["issue"]["labels"] = [{ name: "high priority" }]; + emptyMockConfig(payload.repository.full_name); + + // Triage review was removed long ago — issue is resurfacing and + // should be triaged again. + const old = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); + const scope = nock("https://api.github.com") + .get( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" + ) + .reply(200, [ + { + event: "labeled", + label: { name: "triage review" }, + created_at: old, + }, + { + event: "unlabeled", + label: { name: "triage review" }, + created_at: old, + }, + ]) + .post( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/labels", + (body) => { + expect(body).toMatchObject({ labels: ["triage review"] }); + return true; + } + ) + .reply(200); + + await probot.receive({ name: "issues", payload, id: "2" }); + + scope.done(); + }); + test("add rocm label when issue title contains ROCm", async () => { nock("https://api.github.com") .post("/app/installations/2/access_tokens") From 2109b58c166dd188d8bf56d9678d33bdd2ecd5de Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Mon, 18 May 2026 11:01:50 -0700 Subject: [PATCH 3/3] Fix tsc: pass issue_number explicitly instead of reading from Context --- torchci/lib/bot/autoLabelBot.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/torchci/lib/bot/autoLabelBot.ts b/torchci/lib/bot/autoLabelBot.ts index 9a1769d2e0..6620ccb3bb 100644 --- a/torchci/lib/bot/autoLabelBot.ts +++ b/torchci/lib/bot/autoLabelBot.ts @@ -359,13 +359,14 @@ function getReleaseNotesCategoryAndTopic( export async function wasLabelRecentlyRemoved( context: Context, + issueNumber: number, labelName: string, withinMs: number ): Promise { const events = await context.octokit.paginate( context.octokit.issues.listEvents, context.repo({ - issue_number: context.payload.issue.number, + issue_number: issueNumber, per_page: 100, }) ); @@ -440,6 +441,7 @@ function myBot(app: Probot): void { if ( !(await wasLabelRecentlyRemoved( context, + context.payload.issue.number, "triage review", 60 * 60 * 1000 ))