From 200417524b19c16a3872c826bf08ed9a72dbd917 Mon Sep 17 00:00:00 2001 From: snomiao Date: Wed, 22 Apr 2026 22:05:57 +0900 Subject: [PATCH 01/11] fix(gh-design): skip self-review requests and tolerate 422 errors The 5-minute combined GitHub tasks workflow has been failing every run because GitHub Design Task tried to request review from PabloWiedemann on PRs he authored himself. GitHub rejects this with: 422 Review cannot be requested from pull request author. Since run-gh-tasks.ts exits non-zero on any sub-task failure, this one error takes the whole scheduled job down on every run. Fix: - Filter the PR author out of the reviewers list before calling requestReviewers, so self-authored PRs don't trigger the 422. - Catch 422 responses defensively so an unexpected rejection (e.g. a reviewer who has left the org, a collaborator permission change) does not fail the task. - Persist the reviewers list even when no API call is needed, so the task doesn't re-attempt the same request on every 5-minute tick. Co-Authored-By: Claude Opus 4.7 --- app/tasks/gh-design/gh-design.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index 97601794..f4e01519 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -259,15 +259,27 @@ export async function runGithubDesignTask() { REQUEST_REVIEWERS.some((e) => !task.reviewers?.includes(e)) ) { const requestReviewers = REQUEST_REVIEWERS; - const newReviewers = requestReviewers.filter((e) => !task.reviewers?.includes(e)); - tlog(`Requesting reviewers: ${newReviewers.join(", ")}`); + const newReviewers = requestReviewers.filter( + (e) => !task.reviewers?.includes(e) && e !== task.user, + ); + tlog(`Requesting reviewers: ${newReviewers.join(", ") || "(none)"}`); if (!dryRun) { - await gh.pulls.requestReviewers({ - owner, - repo, - pull_number: issue_number, - reviewers: newReviewers, - }); + if (newReviewers.length > 0) { + try { + await gh.pulls.requestReviewers({ + owner, + repo, + pull_number: issue_number, + reviewers: newReviewers, + }); + } catch (err: any) { + // GitHub returns 422 when the requested reviewer is the PR author + // or otherwise cannot be added. Record the attempt to avoid + // retrying on every 5-minute schedule run. + if (err?.status !== 422) throw err; + tlog(`Reviewer request rejected (422): ${err.message}`); + } + } task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); } } From 4cf8723132db1a019b689cc7b6f1d94d4d8c802f Mon Sep 17 00:00:00 2001 From: sno Date: Fri, 24 Apr 2026 09:17:50 +0900 Subject: [PATCH 02/11] Update app/tasks/gh-design/gh-design.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/tasks/gh-design/gh-design.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index f4e01519..7b25db1b 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -273,8 +273,9 @@ export async function runGithubDesignTask() { reviewers: newReviewers, }); } catch (err: any) { - // GitHub returns 422 when the requested reviewer is the PR author - // or otherwise cannot be added. Record the attempt to avoid + // GitHub may return 422 when a requested reviewer cannot be added, + // such as when they are not a collaborator, cannot be requested, + // or have already been requested. Record the attempt to avoid // retrying on every 5-minute schedule run. if (err?.status !== 422) throw err; tlog(`Reviewer request rejected (422): ${err.message}`); From 33e8f703229b4eb12d1df35aa804d87e95661d49 Mon Sep 17 00:00:00 2001 From: snomiao Date: Wed, 22 Apr 2026 22:05:57 +0900 Subject: [PATCH 03/11] fix(gh-design): skip self-review requests and tolerate 422 errors The 5-minute combined GitHub tasks workflow has been failing every run because GitHub Design Task tried to request review from PabloWiedemann on PRs he authored himself. GitHub rejects this with: 422 Review cannot be requested from pull request author. Since run-gh-tasks.ts exits non-zero on any sub-task failure, this one error takes the whole scheduled job down on every run. Fix: - Filter the PR author out of the reviewers list before calling requestReviewers, so self-authored PRs don't trigger the 422. - Catch 422 responses defensively so an unexpected rejection (e.g. a reviewer who has left the org, a collaborator permission change) does not fail the task. - Persist the reviewers list even when no API call is needed, so the task doesn't re-attempt the same request on every 5-minute tick. Co-Authored-By: Claude Opus 4.7 --- app/tasks/gh-design/gh-design.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index 97601794..f4e01519 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -259,15 +259,27 @@ export async function runGithubDesignTask() { REQUEST_REVIEWERS.some((e) => !task.reviewers?.includes(e)) ) { const requestReviewers = REQUEST_REVIEWERS; - const newReviewers = requestReviewers.filter((e) => !task.reviewers?.includes(e)); - tlog(`Requesting reviewers: ${newReviewers.join(", ")}`); + const newReviewers = requestReviewers.filter( + (e) => !task.reviewers?.includes(e) && e !== task.user, + ); + tlog(`Requesting reviewers: ${newReviewers.join(", ") || "(none)"}`); if (!dryRun) { - await gh.pulls.requestReviewers({ - owner, - repo, - pull_number: issue_number, - reviewers: newReviewers, - }); + if (newReviewers.length > 0) { + try { + await gh.pulls.requestReviewers({ + owner, + repo, + pull_number: issue_number, + reviewers: newReviewers, + }); + } catch (err: any) { + // GitHub returns 422 when the requested reviewer is the PR author + // or otherwise cannot be added. Record the attempt to avoid + // retrying on every 5-minute schedule run. + if (err?.status !== 422) throw err; + tlog(`Reviewer request rejected (422): ${err.message}`); + } + } task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); } } From 7472a007e7b65179eeac2a582836d543e2d2d659 Mon Sep 17 00:00:00 2001 From: sno Date: Fri, 24 Apr 2026 09:17:50 +0900 Subject: [PATCH 04/11] Update app/tasks/gh-design/gh-design.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/tasks/gh-design/gh-design.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index f4e01519..7b25db1b 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -273,8 +273,9 @@ export async function runGithubDesignTask() { reviewers: newReviewers, }); } catch (err: any) { - // GitHub returns 422 when the requested reviewer is the PR author - // or otherwise cannot be added. Record the attempt to avoid + // GitHub may return 422 when a requested reviewer cannot be added, + // such as when they are not a collaborator, cannot be requested, + // or have already been requested. Record the attempt to avoid // retrying on every 5-minute schedule run. if (err?.status !== 422) throw err; tlog(`Reviewer request rejected (422): ${err.message}`); From d5a2921fcf910e9aa538dc86824e09169e05c2a9 Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 10:15:52 +0000 Subject: [PATCH 05/11] =?UTF-8?q?fix(gh-design):=20address=20review=20comm?= =?UTF-8?q?ents=20=E2=80=94=20typed=20catch,=20filter=20author=20from=20pe?= =?UTF-8?q?rsisted=20reviewers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Filter task.user from requestReviewers (not just newReviewers) so persisted data accurately reflects who was actually requested - Use catch (err: unknown) with structural narrowing instead of any for type-safe 422 handling Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/gh-design.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index 7b25db1b..ba683614 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -258,9 +258,9 @@ export async function runGithubDesignTask() { task.type === "pull_request" && REQUEST_REVIEWERS.some((e) => !task.reviewers?.includes(e)) ) { - const requestReviewers = REQUEST_REVIEWERS; + const requestReviewers = REQUEST_REVIEWERS.filter((e) => e !== task.user); const newReviewers = requestReviewers.filter( - (e) => !task.reviewers?.includes(e) && e !== task.user, + (e) => !task.reviewers?.includes(e), ); tlog(`Requesting reviewers: ${newReviewers.join(", ") || "(none)"}`); if (!dryRun) { @@ -272,13 +272,14 @@ export async function runGithubDesignTask() { pull_number: issue_number, reviewers: newReviewers, }); - } catch (err: any) { + } catch (err: unknown) { // GitHub may return 422 when a requested reviewer cannot be added, // such as when they are not a collaborator, cannot be requested, // or have already been requested. Record the attempt to avoid // retrying on every 5-minute schedule run. - if (err?.status !== 422) throw err; - tlog(`Reviewer request rejected (422): ${err.message}`); + const status = (err as { status?: number })?.status; + if (status !== 422) throw err; + tlog(`Reviewer request rejected (422): ${err}`); } } task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); From f97f018731e7766fed5f1f655a99b3be4e87276f Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 10:19:17 +0000 Subject: [PATCH 06/11] test(gh-design): add filterReviewers unit tests Extract reviewer filtering into a testable pure function and add tests covering: self-authored PRs, already-requested reviewers, empty reviewer lists, and undefined alreadyRequested. Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/filterReviewers.spec.ts | 41 +++++++++++++++++++++ app/tasks/gh-design/gh-design.ts | 21 +++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 app/tasks/gh-design/filterReviewers.spec.ts diff --git a/app/tasks/gh-design/filterReviewers.spec.ts b/app/tasks/gh-design/filterReviewers.spec.ts new file mode 100644 index 00000000..d1db2293 --- /dev/null +++ b/app/tasks/gh-design/filterReviewers.spec.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "bun:test"; +import { filterReviewers } from "./gh-design"; + +describe("filterReviewers", () => { + const REVIEWERS = ["PabloWiedemann", "AliceDev"]; + + it("excludes the PR author from both lists", () => { + const result = filterReviewers(REVIEWERS, "PabloWiedemann"); + expect(result.requestReviewers).toEqual(["AliceDev"]); + expect(result.newReviewers).toEqual(["AliceDev"]); + }); + + it("returns all reviewers when author is not in the list", () => { + const result = filterReviewers(REVIEWERS, "SomeoneElse"); + expect(result.requestReviewers).toEqual(["PabloWiedemann", "AliceDev"]); + expect(result.newReviewers).toEqual(["PabloWiedemann", "AliceDev"]); + }); + + it("excludes already-requested reviewers from newReviewers only", () => { + const result = filterReviewers(REVIEWERS, "SomeoneElse", ["PabloWiedemann"]); + expect(result.requestReviewers).toEqual(["PabloWiedemann", "AliceDev"]); + expect(result.newReviewers).toEqual(["AliceDev"]); + }); + + it("returns empty newReviewers when all are already requested", () => { + const result = filterReviewers(REVIEWERS, "SomeoneElse", ["PabloWiedemann", "AliceDev"]); + expect(result.requestReviewers).toEqual(["PabloWiedemann", "AliceDev"]); + expect(result.newReviewers).toEqual([]); + }); + + it("returns empty lists when author is the only reviewer", () => { + const result = filterReviewers(["PabloWiedemann"], "PabloWiedemann"); + expect(result.requestReviewers).toEqual([]); + expect(result.newReviewers).toEqual([]); + }); + + it("handles undefined alreadyRequested as no-one requested yet", () => { + const result = filterReviewers(REVIEWERS, "SomeoneElse", undefined); + expect(result.newReviewers).toEqual(["PabloWiedemann", "AliceDev"]); + }); +}); diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index ba683614..8225d662 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -95,6 +95,20 @@ type GithubDesignTask = { lastDoneAt?: Date | null; // last time this item was processed successfuly }; +/** + * Filter the reviewer list for a PR: excludes the PR author and + * anyone who has already been requested. + */ +export function filterReviewers( + allReviewers: string[], + prAuthor: string, + alreadyRequested?: string[], +): { requestReviewers: string[]; newReviewers: string[] } { + const requestReviewers = allReviewers.filter((e) => e !== prAuthor); + const newReviewers = requestReviewers.filter((e) => !alreadyRequested?.includes(e)); + return { requestReviewers, newReviewers }; +} + // task states const COLLECTION_NAME = "GithubDesignTask"; export const GithubDesignTaskMeta = TaskMetaCollection(COLLECTION_NAME, githubDesignTaskMetaSchema); @@ -258,9 +272,10 @@ export async function runGithubDesignTask() { task.type === "pull_request" && REQUEST_REVIEWERS.some((e) => !task.reviewers?.includes(e)) ) { - const requestReviewers = REQUEST_REVIEWERS.filter((e) => e !== task.user); - const newReviewers = requestReviewers.filter( - (e) => !task.reviewers?.includes(e), + const { requestReviewers, newReviewers } = filterReviewers( + REQUEST_REVIEWERS, + task.user, + task.reviewers, ); tlog(`Requesting reviewers: ${newReviewers.join(", ") || "(none)"}`); if (!dryRun) { From cee9996dc9ec170d782b7bd5583f3c9af0dda4cd Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 10:27:05 +0000 Subject: [PATCH 07/11] refactor(gh-design): extract filterReviewers to side-effect-free module, fix guard condition - Move filterReviewers() into its own module to avoid DB side effects in tests (Copilot review comment) - Fix guard condition: use filtered newReviewers.length instead of comparing against unfiltered REQUEST_REVIEWERS, which caused the task to re-enter and re-save on every 5-minute tick when the author was in the reviewer list (Copilot review comment) Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/filterReviewers.spec.ts | 2 +- app/tasks/gh-design/filterReviewers.ts | 13 ++++++++++ app/tasks/gh-design/gh-design.ts | 28 +++++---------------- 3 files changed, 20 insertions(+), 23 deletions(-) create mode 100644 app/tasks/gh-design/filterReviewers.ts diff --git a/app/tasks/gh-design/filterReviewers.spec.ts b/app/tasks/gh-design/filterReviewers.spec.ts index d1db2293..c62d2d9d 100644 --- a/app/tasks/gh-design/filterReviewers.spec.ts +++ b/app/tasks/gh-design/filterReviewers.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { filterReviewers } from "./gh-design"; +import { filterReviewers } from "./filterReviewers"; describe("filterReviewers", () => { const REVIEWERS = ["PabloWiedemann", "AliceDev"]; diff --git a/app/tasks/gh-design/filterReviewers.ts b/app/tasks/gh-design/filterReviewers.ts new file mode 100644 index 00000000..71a4cc40 --- /dev/null +++ b/app/tasks/gh-design/filterReviewers.ts @@ -0,0 +1,13 @@ +/** + * Filter the reviewer list for a PR: excludes the PR author and + * anyone who has already been requested. + */ +export function filterReviewers( + allReviewers: string[], + prAuthor: string, + alreadyRequested?: string[], +): { requestReviewers: string[]; newReviewers: string[] } { + const requestReviewers = allReviewers.filter((e) => e !== prAuthor); + const newReviewers = requestReviewers.filter((e) => !alreadyRequested?.includes(e)); + return { requestReviewers, newReviewers }; +} diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index 8225d662..8f00e0cb 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -21,6 +21,7 @@ import { planDesignCommentNotification, } from "./slackNotifications"; import { slackMessageUrlParse, slackMessageUrlStringify } from "./slackMessageUrlParse"; +import { filterReviewers } from "./filterReviewers"; const tlog = createTimeLogger(); /** @@ -95,20 +96,6 @@ type GithubDesignTask = { lastDoneAt?: Date | null; // last time this item was processed successfuly }; -/** - * Filter the reviewer list for a PR: excludes the PR author and - * anyone who has already been requested. - */ -export function filterReviewers( - allReviewers: string[], - prAuthor: string, - alreadyRequested?: string[], -): { requestReviewers: string[]; newReviewers: string[] } { - const requestReviewers = allReviewers.filter((e) => e !== prAuthor); - const newReviewers = requestReviewers.filter((e) => !alreadyRequested?.includes(e)); - return { requestReviewers, newReviewers }; -} - // task states const COLLECTION_NAME = "GithubDesignTask"; export const GithubDesignTaskMeta = TaskMetaCollection(COLLECTION_NAME, githubDesignTaskMetaSchema); @@ -268,18 +255,15 @@ export async function runGithubDesignTask() { }); if (task.state === "open") { - if ( - task.type === "pull_request" && - REQUEST_REVIEWERS.some((e) => !task.reviewers?.includes(e)) - ) { + if (task.type === "pull_request") { const { requestReviewers, newReviewers } = filterReviewers( REQUEST_REVIEWERS, task.user, task.reviewers, ); - tlog(`Requesting reviewers: ${newReviewers.join(", ") || "(none)"}`); - if (!dryRun) { - if (newReviewers.length > 0) { + if (newReviewers.length > 0) { + tlog(`Requesting reviewers: ${newReviewers.join(", ")}`); + if (!dryRun) { try { await gh.pulls.requestReviewers({ owner, @@ -296,8 +280,8 @@ export async function runGithubDesignTask() { if (status !== 422) throw err; tlog(`Reviewer request rejected (422): ${err}`); } + task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); } - task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); } } From 0cc2d8f734678753e0235d79d17e177a7bf629a2 Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 10:34:45 +0000 Subject: [PATCH 08/11] fix(gh-design): only persist reviewers on successful API call On 422 rejection, don't save the reviewer list to the database since the reviewers were never actually requested. This keeps DB state accurate and allows retry on subsequent runs. Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/gh-design.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index 8f00e0cb..215bcff5 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -264,6 +264,7 @@ export async function runGithubDesignTask() { if (newReviewers.length > 0) { tlog(`Requesting reviewers: ${newReviewers.join(", ")}`); if (!dryRun) { + let reviewersRequested = false; try { await gh.pulls.requestReviewers({ owner, @@ -271,6 +272,7 @@ export async function runGithubDesignTask() { pull_number: issue_number, reviewers: newReviewers, }); + reviewersRequested = true; } catch (err: unknown) { // GitHub may return 422 when a requested reviewer cannot be added, // such as when they are not a collaborator, cannot be requested, @@ -280,7 +282,9 @@ export async function runGithubDesignTask() { if (status !== 422) throw err; tlog(`Reviewer request rejected (422): ${err}`); } - task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); + if (reviewersRequested) { + task = await saveGithubDesignTask(url, { reviewers: requestReviewers }); + } } } } From e43396f3bbc9100a8975cc511d88e98f14fd076e Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 10:34:53 +0000 Subject: [PATCH 09/11] fix(gh-design): case-insensitive GitHub username comparison GitHub logins are case-insensitive, so normalize comparisons in filterReviewers() to prevent edge cases with mixed-case usernames. Added 2 tests for case-insensitive matching. Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/filterReviewers.spec.ts | 10 ++++++++++ app/tasks/gh-design/filterReviewers.ts | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/tasks/gh-design/filterReviewers.spec.ts b/app/tasks/gh-design/filterReviewers.spec.ts index c62d2d9d..722600e2 100644 --- a/app/tasks/gh-design/filterReviewers.spec.ts +++ b/app/tasks/gh-design/filterReviewers.spec.ts @@ -38,4 +38,14 @@ describe("filterReviewers", () => { const result = filterReviewers(REVIEWERS, "SomeoneElse", undefined); expect(result.newReviewers).toEqual(["PabloWiedemann", "AliceDev"]); }); + + it("compares usernames case-insensitively", () => { + const result = filterReviewers(REVIEWERS, "pablowiedemann"); + expect(result.requestReviewers).toEqual(["AliceDev"]); + }); + + it("matches already-requested reviewers case-insensitively", () => { + const result = filterReviewers(REVIEWERS, "SomeoneElse", ["pablowiedemann"]); + expect(result.newReviewers).toEqual(["AliceDev"]); + }); }); diff --git a/app/tasks/gh-design/filterReviewers.ts b/app/tasks/gh-design/filterReviewers.ts index 71a4cc40..90ed7a9c 100644 --- a/app/tasks/gh-design/filterReviewers.ts +++ b/app/tasks/gh-design/filterReviewers.ts @@ -1,13 +1,16 @@ /** * Filter the reviewer list for a PR: excludes the PR author and * anyone who has already been requested. + * GitHub usernames are case-insensitive, so comparisons are normalized. */ export function filterReviewers( allReviewers: string[], prAuthor: string, alreadyRequested?: string[], ): { requestReviewers: string[]; newReviewers: string[] } { - const requestReviewers = allReviewers.filter((e) => e !== prAuthor); - const newReviewers = requestReviewers.filter((e) => !alreadyRequested?.includes(e)); + const normalizedAuthor = prAuthor.toLowerCase(); + const normalizedRequested = new Set(alreadyRequested?.map((r) => r.toLowerCase()) ?? []); + const requestReviewers = allReviewers.filter((e) => e.toLowerCase() !== normalizedAuthor); + const newReviewers = requestReviewers.filter((e) => !normalizedRequested.has(e.toLowerCase())); return { requestReviewers, newReviewers }; } From 29caf1ffdb381bde3f856b9cffbcb74f09678cb6 Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 10:42:38 +0000 Subject: [PATCH 10/11] docs(gh-design): clarify 422 retry behavior in comment Update comment to accurately state that 422 rejections are retried on subsequent runs (not suppressed), since the reviewer may become eligible later. Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/gh-design.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/tasks/gh-design/gh-design.ts b/app/tasks/gh-design/gh-design.ts index 215bcff5..1e9af828 100644 --- a/app/tasks/gh-design/gh-design.ts +++ b/app/tasks/gh-design/gh-design.ts @@ -275,9 +275,9 @@ export async function runGithubDesignTask() { reviewersRequested = true; } catch (err: unknown) { // GitHub may return 422 when a requested reviewer cannot be added, - // such as when they are not a collaborator, cannot be requested, - // or have already been requested. Record the attempt to avoid - // retrying on every 5-minute schedule run. + // such as when they are not a collaborator or cannot be requested. + // We log but don't persist, so the request will be retried on the + // next run (the reviewer may become eligible later). const status = (err as { status?: number })?.status; if (status !== 422) throw err; tlog(`Reviewer request rejected (422): ${err}`); From add499b35542f7c7baaf7d2037c5bb2ad79c2109 Mon Sep 17 00:00:00 2001 From: snomiao Date: Thu, 30 Apr 2026 11:13:16 +0000 Subject: [PATCH 11/11] docs(gh-design): fix filterReviewers docstring to match behavior Clarify that requestReviewers excludes only the author while newReviewers additionally excludes already-requested reviewers. Amp-Thread-ID: https://ampcode.com/threads/T-019ddde1-db08-71ca-809d-fd7e51b68842 Co-authored-by: Amp --- app/tasks/gh-design/filterReviewers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/tasks/gh-design/filterReviewers.ts b/app/tasks/gh-design/filterReviewers.ts index 90ed7a9c..dc0aac72 100644 --- a/app/tasks/gh-design/filterReviewers.ts +++ b/app/tasks/gh-design/filterReviewers.ts @@ -1,6 +1,7 @@ /** - * Filter the reviewer list for a PR: excludes the PR author and - * anyone who has already been requested. + * Compute eligible reviewers for a PR. + * Returns `requestReviewers` (all reviewers minus the PR author) and + * `newReviewers` (eligible reviewers not yet requested). * GitHub usernames are case-insensitive, so comparisons are normalized. */ export function filterReviewers(