Skip to content

fix(github_graphql): resolve Bot actors so bot-authored PRs keep their author - #9086

Open
zyv4yk wants to merge 1 commit into
apache:mainfrom
zyv4yk:fix/9085-graphql-bot-author
Open

fix(github_graphql): resolve Bot actors so bot-authored PRs keep their author#9086
zyv4yk wants to merge 1 commit into
apache:mainfrom
zyv4yk:fix/9085-graphql-bot-author

Conversation

@zyv4yk

@zyv4yk zyv4yk commented Aug 31, 2026

Copy link
Copy Markdown

⚠️ Pre Checklist

  • I have read through the Contributing Documentation.
  • I have added relevant tests.
  • I have added relevant documentation.
  • I will add labels to the PR, such as pr-type/bug-fix, pr-type/feature-development, etc.

Summary

When the GitHub connection runs on GraphQL, every pull request, issue and review opened by an actor of type Bot — GitHub Apps, Dependabot, Renovate, GitHub Actions — is stored with no author: author_name = '', author_id = 0, and no pull_requests.author_id in the domain layer.

author, mergedBy and review authors are typed Actor in the GitHub schema, and Actor is implemented by User, Bot, Organization, Mannequin and EnterpriseUserAccount. GraphqlInlineAccountQuery spread only ... on User, so a Bot author came back as an empty selection set and the Id == 0 guard in extractGraphqlPreAccount then dropped the account. The REST collector is unaffected, which is why the same repo shows bot PRs on REST and none on GraphQL.

The fragment cannot just be added to the existing struct. GraphqlInlineAccountQuery is reused for fields the schema types as User — PR and issue assignees, and commit.author.user — and GitHub rejects the entire query when ... on Bot is spread there:

Fragment on Bot can't be spread inside User

So this PR splits the two cases:

  • GraphqlInlineAccountQuery — unchanged shape, for User-typed fields (assignees, commit authors).
  • GraphqlInlineActorQuery... on User + ... on Bot, for Actor-typed fields (PullRequest.author, PullRequest.mergedBy, PullRequestReview.author, Issue.author).

Both expose Account() GithubAccountEdge, so the extractors read one normalized account and stop caring which type the actor resolved to. The accessor is nil-safe, which also removes the != nil checks at the call sites.

Two details in the normalization:

  1. Bot.login has no [bot] suffix. GraphQL returns dependabot, REST returns dependabot[bot], both with databaseId 49699333. The suffix is appended so one actor does not end up with two logins depending on the collector. This also keeps the is_bot flag from feat(dora): exclude bot/automation accounts from PR Pickup Time calcu… #9000 working: its fallback is strings.HasSuffix(login, "[bot]"), and _tool_github_accounts.type is never populated on the GraphQL path.
  2. Bot exposes a narrower field set than User — no name, company or email — hence a separate GithubBotEdge. Name, Company and Email are left empty, which is exactly what the REST collector stores for a bot (GET /users/dependabot[bot] returns null for all three).

One follow-on: Collect Users looks bot logins up through user(login:), which resolves Users only and answers Could not resolve to a User with the login of 'dependabot[bot]'. Errors there are already ignored, but now that bot logins reach _tool_github_repo_accounts that would be one guaranteed NOT_FOUND per bot on every sync, so the cursor skips them. Bots still become accounts — ConvertAccounts reads _tool_github_repo_accounts and only enriches from _tool_github_accounts when a row exists.

Does this close any open issues?

Closes #9085

Screenshots

Running the query this PR generates against the live API, nodes { author { login databaseId } } on grafana/grafana:

before after
#131864 "" / 0 cursor / 206951365
#131868 review authors "", "" github-actions, cursor
#131851 review authors "", "", "" copilot-pull-request-reviewer, cursor, PranshulSoni

The single-field version of the same check, which needs nothing but a token:

gh api graphql -f query='
{
  search(query: "repo:grafana/grafana is:pr author:app/dependabot", type: ISSUE, first: 1) {
    nodes { ... on PullRequest {
      number
      userFragmentOnly: author { ... on User { login databaseId } }
      withBotFragment:  author { __typename ... on User { login databaseId } ... on Bot { login databaseId } }
    } }
  }
}'

{"number":131544,
 "userFragmentOnly":{},
 "withBotFragment":{"__typename":"Bot","login":"dependabot","databaseId":49699333}}

Other Information

Tests. account_graphql_pre_extractor_test.go covers the accessor for both query types (user, bot, bot login that already carries the suffix, unresolved actor, nil), the raw-layer JSON round trip in both the new and the pre-fix shape, and — the part that actually guards the regression — graphql.ConstructQuery output, asserting ... on Bot reaches the actor fields and never the User-typed ones. go build, go vet and golangci-lint run ./plugins/github_graphql/... are clean. The e2e package needs E2E_DB_URL and was not run locally; the only github_graphql e2e fixture covers deployments, which this PR does not touch.

Existing data. The author is already empty in _raw_github_graphql_prs, so rows collected before this fix are not repaired by re-running extract or convert — they need a re-collect in Full Refresh mode.

Labels. I do not have permission to set them; this is pr-type/bug-fix.

…r author

`author`, `mergedBy` and review authors are typed `Actor` in the GitHub
schema, which `Bot` implements alongside `User`. The inline query spread
only `... on User`, so every pull request, issue and review opened by a
GitHub App, Dependabot or Renovate came back with an empty selection set
and was stored with no author at all.

`... on Bot` cannot simply be added to the existing query: GitHub rejects
the whole request with "Fragment on Bot can't be spread inside User" when
it is spread on a `User`-typed field such as assignees or a commit author.
Actor fields therefore get their own GraphqlInlineActorQuery, and both
types resolve through an Account() accessor.

Bot logins are normalized to the `[bot]` suffix the REST collector stores,
so an account keeps one identity across collectors, and the accounts
lookup skips bot logins because `user(login:)` never resolves them.

Fixes apache#9085

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug][github_graphql] PR/issue author is empty for Bot actors (GitHub Apps, Dependabot) — GraphQL query only spreads "... on User"

1 participant