fix(github): match bot actors in comment filters using GraphQL __typename (#1616)

`exclude_comments_by_actor` and `include_comments_by_actor` never matched
any bot. Both the documented `*[bot]` wildcard and exact entries such as
`dependabot[bot]` silently did nothing.

GitHub's GraphQL API returns the bare login for App actors ("dependabot"),
while REST and the GitHub UI append a suffix ("dependabot[bot]"). Filter
patterns are written in the suffixed form, so matching a GraphQL login
against them could never succeed and `actor.endsWith("[bot]")` was dead
code.

Request `__typename` on the Actor-typed author selections and normalize
App actors to their suffixed name via `resolveActorName()` before matching.
Normalizing at the filter boundary fixes the wildcard and exact-match cases
together, and leaves the author names shown in the prompt unchanged.

The existing test mocked `login: "scanner[bot]"`, a payload GraphQL never
produces, which is why the gap was invisible. It now mocks the real shape
(`__typename: "Bot", login: "scanner"`) and fails without this fix.

The commit author selection is left alone: it is a GitCommit, not an Actor.

Fixes #1514

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Neal Daftary
2026-08-18 17:24:26 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 0a80d21df7
commit 65b50df083
6 changed files with 93 additions and 5 deletions
+47
View File
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
import {
parseActorFilter,
actorMatchesPattern,
resolveActorName,
shouldIncludeCommentByActor,
} from "../src/github/utils/actor-filter";
@@ -170,3 +171,49 @@ describe("shouldIncludeCommentByActor", () => {
).toBe(false);
});
});
describe("resolveActorName", () => {
test("appends the [bot] suffix to GraphQL App actors", () => {
// GraphQL returns the bare login for bots; REST would say "dependabot[bot]".
expect(resolveActorName({ __typename: "Bot", login: "dependabot" })).toBe(
"dependabot[bot]",
);
});
test("leaves human logins untouched", () => {
expect(resolveActorName({ __typename: "User", login: "octocat" })).toBe(
"octocat",
);
});
test("does not double-suffix a login that already ends with [bot]", () => {
expect(
resolveActorName({ __typename: "Bot", login: "dependabot[bot]" }),
).toBe("dependabot[bot]");
});
test("maps deleted accounts to ghost", () => {
expect(resolveActorName(null)).toBe("ghost");
expect(resolveActorName(undefined)).toBe("ghost");
});
test("falls back to the login when __typename is absent", () => {
expect(resolveActorName({ login: "octocat" })).toBe("octocat");
});
test("a bot actor matches the *[bot] wildcard once resolved", () => {
const actor = resolveActorName({ __typename: "Bot", login: "renovate" });
expect(actorMatchesPattern(actor, "*[bot]")).toBe(true);
// The raw GraphQL login never matches, which is the bug being fixed.
expect(actorMatchesPattern("renovate", "*[bot]")).toBe(false);
});
test("a bot actor matches an exact [bot] pattern once resolved", () => {
const actor = resolveActorName({ __typename: "Bot", login: "dependabot" });
expect(shouldIncludeCommentByActor(actor, [], ["dependabot[bot]"])).toBe(
false,
);
});
});
+4 -1
View File
@@ -1215,7 +1215,10 @@ describe("fetchGitHubData integration with time filtering", () => {
{
id: "2",
databaseId: "2",
author: { login: "scanner[bot]" },
// GraphQL returns the bare login for App actors plus
// __typename: "Bot". It does NOT append a "[bot]" suffix the
// way REST does, so this mirrors a real payload.
author: { __typename: "Bot", login: "scanner" },
body: "Pre-trigger bot review",
state: "COMMENTED",
submittedAt: "2024-01-15T11:00:00Z",