From fada731a578533f35819de27de746071df498ea5 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:36:28 -0400 Subject: [PATCH 1/4] feat: request bot as PR reviewer via GraphQL botIds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The REST API `POST /pulls/{n}/requested_reviewers` silently ignores bot accounts — GitHub returns 200 but never adds the bot. The existing workflow step was dead code. Replace it with the GraphQL `requestReviews` mutation's `botIds` field, which is the only API that supports requesting bots as PR reviewers. The action now self-requests before posting its review: discovers its identity via `viewer { login }`, resolves the bot's node ID via REST `GET /users/{slug}[bot]`, and calls `requestReviews` with `botIds`. Best-effort — failures are logged but never block the review pipeline. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/self_review.yml | 8 --- fixtures/pull.get.json | 1 + fixtures/pull_request.opened.json | 1 + src/__tests__/orchestrate.test.ts | 40 +++++++++++ src/github/__tests__/client.test.ts | 101 ++++++++++++++++++++++++++++ src/github/__tests__/event.test.ts | 2 + src/github/client.ts | 43 ++++++++++++ src/github/event.ts | 3 + src/orchestrate.ts | 11 +++ src/review/__tests__/prompt.test.ts | 1 + 10 files changed, 203 insertions(+), 8 deletions(-) diff --git a/.github/workflows/self_review.yml b/.github/workflows/self_review.yml index 5285a78..930d9f0 100644 --- a/.github/workflows/self_review.yml +++ b/.github/workflows/self_review.yml @@ -39,14 +39,6 @@ jobs: permission-contents: read permission-pull-requests: write - - name: Request review from umm-actually bot - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} - run: | - gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/requested_reviewers" \ - --method POST -f 'reviewers[]=umm-actually[bot]' || true - - uses: ./ with: github_token: ${{ steps.app-token.outputs.token }} diff --git a/fixtures/pull.get.json b/fixtures/pull.get.json index 540c15d..81d0e36 100644 --- a/fixtures/pull.get.json +++ b/fixtures/pull.get.json @@ -1,5 +1,6 @@ { "number": 7, + "node_id": "PR_kwDOMock7", "state": "open", "title": "feat: trim names before greeting", "body": "Trims whitespace from names and validates registry keys.", diff --git a/fixtures/pull_request.opened.json b/fixtures/pull_request.opened.json index 32a7a0c..51394be 100644 --- a/fixtures/pull_request.opened.json +++ b/fixtures/pull_request.opened.json @@ -3,6 +3,7 @@ "number": 7, "pull_request": { "number": 7, + "node_id": "PR_kwDOMock7", "title": "feat: trim names before greeting", "body": "Trims whitespace from names and validates registry keys.", "head": { diff --git a/src/__tests__/orchestrate.test.ts b/src/__tests__/orchestrate.test.ts index 2df44c0..ad4bfc0 100644 --- a/src/__tests__/orchestrate.test.ts +++ b/src/__tests__/orchestrate.test.ts @@ -59,6 +59,7 @@ const fixtureReviewResponse: ReviewResponse = JSON.parse( const fixturePrContext: PrContext = { prNumber: 7, + nodeId: "PR_kwDOMock7", title: "feat: trim names before greeting", body: "Trims whitespace from names and validates registry keys.", headSha: "abc123def456abc123def456abc123def456abc1", @@ -191,6 +192,7 @@ type RecordingStubs = { deps: OrchestrateDeps fetchPullRequestCalls: { prNumber: number }[] fetchDiffCalls: { prNumber: number }[] + requestBotReviewCalls: { prNodeId: string }[] submitReviewCalls: SubmitReviewParams[] fetchReviewCommentsCalls: { prNumber: number }[] upsertSummaryCommentCalls: UpsertSummaryCommentParams[] @@ -228,6 +230,8 @@ const makeOrchestrateDeps = ( ...overrides.fixtureResult, } + const requestBotReviewCalls: { prNodeId: string }[] = [] + const githubClient: GithubClient = { fetchPullRequest: async (params) => { fetchPullRequestCalls.push(params) @@ -237,6 +241,9 @@ const makeOrchestrateDeps = ( fetchDiffCalls.push(params) return { kind: "ok" as const, diff: sampleDiff } }, + requestBotReview: async (params) => { + requestBotReviewCalls.push(params) + }, submitReview: async (params) => { submitReviewCalls.push(params) return { @@ -292,6 +299,7 @@ const makeOrchestrateDeps = ( deps, fetchPullRequestCalls, fetchDiffCalls, + requestBotReviewCalls, submitReviewCalls, fetchReviewCommentsCalls, upsertSummaryCommentCalls, @@ -681,6 +689,38 @@ describe("orchestrate", () => { expect(stubs.fetchPullRequestCalls).toHaveLength(0) }) + it("continues when requestBotReview throws", async () => { + const stubs = makeOrchestrateDeps({ + githubClient: { + requestBotReview: async () => { + throw new Error("GraphQL viewer query failed") + }, + }, + }) + const logger = createTestLogger() + + const result = await orchestrate(stubs.deps, logger) + + expect(result.findingsCount).toBeGreaterThanOrEqual(0) + expect(result.reviewUrl).toBeTruthy() + expect(logger.messages).toContainEqual({ + level: "warn", + message: "failed to request bot review", + data: { error: "[Error]: GraphQL viewer query failed" }, + }) + }) + + it("calls requestBotReview with the PR node ID", async () => { + const stubs = makeOrchestrateDeps() + const logger = createTestLogger() + + await orchestrate(stubs.deps, logger) + + expect(stubs.requestBotReviewCalls).toEqual([ + { prNodeId: "PR_kwDOMock7" }, + ]) + }) + it("filters non-findings from LLM output", async () => { const nonFinding = makeFinding({ line: 3, diff --git a/src/github/__tests__/client.test.ts b/src/github/__tests__/client.test.ts index 39bfe1d..ccbdb46 100644 --- a/src/github/__tests__/client.test.ts +++ b/src/github/__tests__/client.test.ts @@ -15,6 +15,8 @@ const pullGetResponse: Record = JSON.parse( type StubResponse = { data: unknown } | { error: unknown } /** Records every call; replays queued responses in order, throwing queued errors. */ +type GraphqlResponse = { data: unknown } | { error: unknown } + const makeOctokitStub = ({ getResponses = [], createReviewResponses = [], @@ -22,6 +24,8 @@ const makeOctokitStub = ({ listCommentsResponses = [], createCommentResponses = [], updateCommentResponses = [], + getByUsernameResponses = [], + graphqlResponses = [], }: { getResponses?: StubResponse[] createReviewResponses?: StubResponse[] @@ -29,6 +33,8 @@ const makeOctokitStub = ({ listCommentsResponses?: StubResponse[] createCommentResponses?: StubResponse[] updateCommentResponses?: StubResponse[] + getByUsernameResponses?: StubResponse[] + graphqlResponses?: GraphqlResponse[] } = {}) => { const getCalls: Record[] = [] const createReviewCalls: Record[] = [] @@ -36,6 +42,11 @@ const makeOctokitStub = ({ const listCommentsCalls: Record[] = [] const createCommentCalls: Record[] = [] const updateCommentCalls: Record[] = [] + const getByUsernameCalls: Record[] = [] + const graphqlCalls: { + query: string + parameters?: Record + }[] = [] const takeNext = ( queue: StubResponse[], @@ -51,7 +62,30 @@ const makeOctokitStub = ({ } const octokit: OctokitLike = { + graphql: ( + query: string, + parameters?: Record, + ): Promise => { + graphqlCalls.push({ query, ...(parameters ? { parameters } : {}) }) + const next = graphqlResponses[graphqlCalls.length - 1] + if (next === undefined) { + throw new Error(`stub: unexpected graphql call #${graphqlCalls.length}`) + } + if ("error" in next) return Promise.reject(next.error) + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- test stub must satisfy the generic signature + return Promise.resolve(next.data as T) + }, rest: { + users: { + getByUsername: async (params: Record) => { + getByUsernameCalls.push(params) + return takeNext( + getByUsernameResponses, + getByUsernameCalls.length, + "users.getByUsername", + ) + }, + }, pulls: { get: async (params) => { getCalls.push(params) @@ -111,6 +145,8 @@ const makeOctokitStub = ({ listCommentsCalls, createCommentCalls, updateCommentCalls, + getByUsernameCalls, + graphqlCalls, } } @@ -154,6 +190,7 @@ describe("fetchPullRequest", () => { ]) expect(prContext).toEqual({ prNumber: 7, + nodeId: "PR_kwDOMock7", title: "feat: trim names before greeting", body: "Trims whitespace from names and validates registry keys.", headSha: "abc123def456abc123def456abc123def456abc1", @@ -250,6 +287,70 @@ describe("fetchDiff", () => { }) }) +describe("requestBotReview", () => { + it("discovers the bot identity via REST and requests review via GraphQL", async () => { + const stub = makeOctokitStub({ + graphqlResponses: [ + { data: { viewer: { login: "umm-actually" } } }, + { data: { requestReviews: { pullRequest: { id: "PR_kwDOMock7" } } } }, + ], + getByUsernameResponses: [ + { + data: { + node_id: "BOT_kgDOEewBdQ", + login: "umm-actually[bot]", + type: "Bot", + }, + }, + ], + }) + const { client, logger } = makeClient(stub) + + await client.requestBotReview({ prNodeId: "PR_kwDOMock7" }) + + expect(stub.graphqlCalls).toHaveLength(2) + expect(stub.graphqlCalls[0]?.query).toContain("viewer") + expect(stub.getByUsernameCalls).toEqual([{ username: "umm-actually[bot]" }]) + expect(stub.graphqlCalls[1]?.parameters).toEqual({ + prId: "PR_kwDOMock7", + botIds: ["BOT_kgDOEewBdQ"], + }) + expect(logger.messages).toContainEqual({ + level: "info", + message: "requested bot review", + data: { login: "umm-actually[bot]" }, + }) + }) + + it("logs a warning when the bot user response is malformed", async () => { + const stub = makeOctokitStub({ + graphqlResponses: [{ data: { viewer: { login: "umm-actually" } } }], + getByUsernameResponses: [{ data: { message: "Not Found" } }], + }) + const { client, logger } = makeClient(stub) + + await client.requestBotReview({ prNodeId: "PR_kwDOMock7" }) + + expect(stub.graphqlCalls).toHaveLength(1) + expect(logger.messages).toContainEqual({ + level: "warn", + message: "could not resolve bot user for review request", + data: { botLogin: "umm-actually[bot]" }, + }) + }) + + it("propagates errors from the viewer query", async () => { + const stub = makeOctokitStub({ + graphqlResponses: [{ error: new Error("token expired") }], + }) + const { client } = makeClient(stub) + + await expect( + client.requestBotReview({ prNodeId: "PR_kwDOMock7" }), + ).rejects.toThrow("token expired") + }) +}) + describe("submitReview", () => { const reviewUrl = "https://github.com/aliasunder/fixture/pull/7#pullrequestreview-1" diff --git a/src/github/__tests__/event.test.ts b/src/github/__tests__/event.test.ts index 2e59a3e..f974c7c 100644 --- a/src/github/__tests__/event.test.ts +++ b/src/github/__tests__/event.test.ts @@ -31,6 +31,7 @@ describe("resolvePullRequestEvent", () => { kind: "complete", context: { prNumber: 7, + nodeId: "PR_kwDOMock7", title: "feat: trim names before greeting", body: "Trims whitespace from names and validates registry keys.", headSha: "abc123def456abc123def456abc123def456abc1", @@ -54,6 +55,7 @@ describe("resolvePullRequestEvent", () => { kind: "complete", context: { prNumber: 7, + nodeId: "PR_kwDOMock7", title: "feat: trim names before greeting", body: "Trims whitespace from names and validates registry keys.", headSha: "abc123def456abc123def456abc123def456abc1", diff --git a/src/github/client.ts b/src/github/client.ts index 5169726..cf46357 100644 --- a/src/github/client.ts +++ b/src/github/client.ts @@ -12,7 +12,14 @@ import type { PrContext } from "./event.js" * real octokit assignable under strictFunctionTypes. */ export type OctokitLike = { + graphql( + query: string, + parameters?: Record, + ): Promise rest: { + users: { + getByUsername(params: { username: string }): Promise<{ data: unknown }> + } pulls: { get(params: { owner: string @@ -70,6 +77,7 @@ export type UpsertCommentResult = { url: string; created: boolean } export type GithubClient = { fetchPullRequest: (params: { prNumber: number }) => Promise fetchDiff: (params: { prNumber: number }) => Promise + requestBotReview: (params: { prNodeId: string }) => Promise submitReview: (params: { prNumber: number commitId: string @@ -93,6 +101,7 @@ export type GithubClient = { * pulls.get response must provide to build a PrContext. */ const prResponseSchema = z.object({ number: z.int().positive(), + node_id: z.string(), title: z.string(), body: z.string().nullable(), head: z.object({ sha: z.string(), ref: z.string() }), @@ -155,6 +164,7 @@ export const createGithubClient = ( const pullRequest = parsed.data return { prNumber: pullRequest.number, + nodeId: pullRequest.node_id, title: pullRequest.title, body: pullRequest.body, headSha: pullRequest.head.sha, @@ -191,6 +201,38 @@ export const createGithubClient = ( } } + const botNodeIdSchema = z.object({ node_id: z.string(), login: z.string() }) + + const requestBotReview = async ({ + prNodeId, + }: { + prNodeId: string + }): Promise => { + const viewer = await octokit.graphql<{ + viewer: { login: string } + }>("query { viewer { login } }") + const botLogin = `${viewer.viewer.login}[bot]` + + const response = await octokit.rest.users.getByUsername({ + username: botLogin, + }) + const parsed = botNodeIdSchema.safeParse(response.data) + if (!parsed.success) { + logger.warn("could not resolve bot user for review request", { botLogin }) + return + } + + await octokit.graphql( + `mutation($prId: ID!, $botIds: [ID!]!) { + requestReviews(input: { pullRequestId: $prId, botIds: $botIds }) { + pullRequest { id } + } + }`, + { prId: prNodeId, botIds: [parsed.data.node_id] }, + ) + logger.info("requested bot review", { login: parsed.data.login }) + } + const submitReview = async ({ prNumber, commitId, @@ -347,6 +389,7 @@ export const createGithubClient = ( return { fetchPullRequest, fetchDiff, + requestBotReview, submitReview, fetchReviewComments, upsertSummaryComment, diff --git a/src/github/event.ts b/src/github/event.ts index 7732f87..dd70e54 100644 --- a/src/github/event.ts +++ b/src/github/event.ts @@ -4,6 +4,7 @@ import type { Logger } from "../logger.js" /** Everything the review pipeline needs to know about the PR under review. */ export type PrContext = { prNumber: number + nodeId: string title: string body: string | null headSha: string @@ -23,6 +24,7 @@ export type ResolvedEvent = const pullRequestEventSchema = z.object({ pull_request: z.object({ number: z.int().positive(), + node_id: z.string(), title: z.string(), body: z.string().nullable(), head: z.object({ sha: z.string(), ref: z.string() }), @@ -65,6 +67,7 @@ export const resolvePullRequestEvent = ( kind: "complete", context: { prNumber: pullRequest.number, + nodeId: pullRequest.node_id, title: pullRequest.title, body: pullRequest.body, headSha: pullRequest.head.sha, diff --git a/src/orchestrate.ts b/src/orchestrate.ts index 01d6acc..76e039d 100644 --- a/src/orchestrate.ts +++ b/src/orchestrate.ts @@ -160,6 +160,17 @@ export const orchestrate = async ( prNumber: resolvedEvent.prNumber, }) + // Step 3.5: request the bot as a PR reviewer (best-effort) + try { + await githubClient.requestBotReview({ prNodeId: prContext.nodeId }) + } catch (requestError) { + const errorDetail = + requestError instanceof Error + ? `[${requestError.name}]: ${requestError.message}` + : String(requestError) + logger.warn("failed to request bot review", { error: errorDetail }) + } + const postSkipReview = async (reason: string): Promise => { const body = buildSkipBody(reason) const { url } = await githubClient.submitReview({ diff --git a/src/review/__tests__/prompt.test.ts b/src/review/__tests__/prompt.test.ts index 9fb70b5..06a185e 100644 --- a/src/review/__tests__/prompt.test.ts +++ b/src/review/__tests__/prompt.test.ts @@ -16,6 +16,7 @@ if (combinedPhase === undefined) const prContext: PrContext = { prNumber: 7, + nodeId: "PR_kwDOMock7", title: "feat: trim names before greeting", body: "Trims whitespace from names.", headSha: "abc123", From 59828a0efd5b4033b167380b17663b66b6988e23 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:45:16 -0400 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20bump=20create-github-app-token=20v?= =?UTF-8?q?2=20=E2=86=92=20v3.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node.js 20 is deprecated; the v2 action targets Node 20 and triggers a runner warning. v3.2.0 targets Node 22. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/release.yml | 2 +- .github/workflows/self_review.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c0a3ae..c527a7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ jobs: permissions: contents: write steps: - - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: app-token with: app-id: ${{ secrets.RELEASE_APP_ID }} diff --git a/.github/workflows/self_review.yml b/.github/workflows/self_review.yml index 930d9f0..4dd6f18 100644 --- a/.github/workflows/self_review.yml +++ b/.github/workflows/self_review.yml @@ -31,7 +31,7 @@ jobs: with: persist-credentials: false - - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: app-token with: app-id: ${{ secrets.UMM_APP_ID }} From dd6381fe60603c10b3421f2dd590895dc5e1002a Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:23:53 -0400 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20address=20review=20findings=20?= =?UTF-8?q?=E2=80=94=20test=20gaps,=20schema=20hardening,=20assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests for requestReviews mutation error, getByUsername REST error, and non-Bot type rejection. Tighten botNodeIdSchema to validate type: "Bot" for defense-in-depth. Replace loose assertions (toContain, toBeTruthy, toBeGreaterThanOrEqual) with exact matches. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/__tests__/orchestrate.test.ts | 4 +- src/github/__tests__/client.test.ts | 62 ++++++++++++++++++++++++++++- src/github/client.ts | 6 ++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/__tests__/orchestrate.test.ts b/src/__tests__/orchestrate.test.ts index ad4bfc0..2f0fbca 100644 --- a/src/__tests__/orchestrate.test.ts +++ b/src/__tests__/orchestrate.test.ts @@ -701,8 +701,8 @@ describe("orchestrate", () => { const result = await orchestrate(stubs.deps, logger) - expect(result.findingsCount).toBeGreaterThanOrEqual(0) - expect(result.reviewUrl).toBeTruthy() + expect(result.findingsCount).toBe(expectedSelection.selected.length) + expect(result.reviewUrl).toBe("https://github.com/test/review/1") expect(logger.messages).toContainEqual({ level: "warn", message: "failed to request bot review", diff --git a/src/github/__tests__/client.test.ts b/src/github/__tests__/client.test.ts index ccbdb46..8e84ec1 100644 --- a/src/github/__tests__/client.test.ts +++ b/src/github/__tests__/client.test.ts @@ -309,7 +309,7 @@ describe("requestBotReview", () => { await client.requestBotReview({ prNodeId: "PR_kwDOMock7" }) expect(stub.graphqlCalls).toHaveLength(2) - expect(stub.graphqlCalls[0]?.query).toContain("viewer") + expect(stub.graphqlCalls[0]?.query).toBe("query { viewer { login } }") expect(stub.getByUsernameCalls).toEqual([{ username: "umm-actually[bot]" }]) expect(stub.graphqlCalls[1]?.parameters).toEqual({ prId: "PR_kwDOMock7", @@ -349,6 +349,66 @@ describe("requestBotReview", () => { client.requestBotReview({ prNodeId: "PR_kwDOMock7" }), ).rejects.toThrow("token expired") }) + + it("propagates errors from the getByUsername REST call", async () => { + const stub = makeOctokitStub({ + graphqlResponses: [{ data: { viewer: { login: "umm-actually" } } }], + getByUsernameResponses: [{ error: makeStatusError(404) }], + }) + const { client } = makeClient(stub) + + await expect( + client.requestBotReview({ prNodeId: "PR_kwDOMock7" }), + ).rejects.toThrow("HTTP 404") + }) + + it("propagates errors from the requestReviews mutation", async () => { + const stub = makeOctokitStub({ + graphqlResponses: [ + { data: { viewer: { login: "umm-actually" } } }, + { error: new Error("insufficient permissions") }, + ], + getByUsernameResponses: [ + { + data: { + node_id: "BOT_kgDOEewBdQ", + login: "umm-actually[bot]", + type: "Bot", + }, + }, + ], + }) + const { client } = makeClient(stub) + + await expect( + client.requestBotReview({ prNodeId: "PR_kwDOMock7" }), + ).rejects.toThrow("insufficient permissions") + }) + + it("rejects a non-Bot user type", async () => { + const stub = makeOctokitStub({ + graphqlResponses: [{ data: { viewer: { login: "some-app" } } }], + getByUsernameResponses: [ + { + data: { + node_id: "MDQ6VXNlcjEyMzQ=", + login: "some-app[bot]", + type: "User", + }, + }, + ], + }) + const { client, logger } = makeClient(stub) + + await client.requestBotReview({ prNodeId: "PR_kwDOMock7" }) + + expect(stub.graphqlCalls).toHaveLength(1) + expect(logger.messages).toContainEqual({ + level: "warn", + message: "could not resolve bot user for review request", + data: { botLogin: "some-app[bot]" }, + }) + }) }) describe("submitReview", () => { diff --git a/src/github/client.ts b/src/github/client.ts index cf46357..6056ce3 100644 --- a/src/github/client.ts +++ b/src/github/client.ts @@ -201,7 +201,11 @@ export const createGithubClient = ( } } - const botNodeIdSchema = z.object({ node_id: z.string(), login: z.string() }) + const botNodeIdSchema = z.object({ + node_id: z.string(), + login: z.string(), + type: z.literal("Bot"), + }) const requestBotReview = async ({ prNodeId, From ec7a172099dad9d247b26707214097fc48440ba5 Mon Sep 17 00:00:00 2001 From: Tanisha Aberdeen <32620895+aliasunder@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:26:11 -0400 Subject: [PATCH 4/4] fix: add min(1) to botNodeIdSchema string validators Co-Authored-By: Claude Opus 4.6 (1M context) --- src/github/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/github/client.ts b/src/github/client.ts index 6056ce3..61c0681 100644 --- a/src/github/client.ts +++ b/src/github/client.ts @@ -202,8 +202,8 @@ export const createGithubClient = ( } const botNodeIdSchema = z.object({ - node_id: z.string(), - login: z.string(), + node_id: z.string().min(1), + login: z.string().min(1), type: z.literal("Bot"), })