Search before asking
What happened
When the GitHub connection has Use GraphQL APIs enabled, every pull request, issue and review opened by an actor of GraphQL type Bot (GitHub Apps, Dependabot, Renovate, GitHub Actions, any AI code-review or automation app) is stored with an empty author: author_name = '' and author_id = 0 in _tool_github_pull_requests, and no pull_requests.author_id in the domain layer.
The data is already empty in the raw layer, so it cannot be recovered by re-running the extractor:
"Author": {"Login": "", "Id": 0, "Name": "", "Company": "", "Email": "", ...}
Root cause is in backend/plugins/github_graphql/tasks/account_graphql_pre_extractor.go:
type GraphqlInlineAccountQuery struct {
GithubAccountEdge `graphql:"... on User"`
}
func extractGraphqlPreAccount(result *[]interface{}, res *GraphqlInlineAccountQuery, repoId int, connId uint64) {
if res == nil || res.Id == 0 {
return
}
author, mergedBy, assignees and review authors are all typed as Actor in the GitHub schema. Actor is implemented by User, Bot, Organization, Mannequin and EnterpriseUserAccount. The query spreads only ... on User, so for any non-User actor GitHub returns an empty selection set, and the res.Id == 0 guard then drops the account entirely.
The REST collector is not affected — it resolves bot authors correctly — which is why a connection using GraphQL shows almost no bot-authored PRs while a REST one does.
What do you expect to happen
PRs, issues and reviews opened by GitHub Apps / bots should carry their author, with the same identity the REST collector produces (dependabot[bot], id 49699333), so that accounts rows, pull_requests.author_id and any bot-aware metric work the same regardless of which collector is used.
How to reproduce
The behaviour can be shown against the GitHub API directly, no DevLake instance needed. This query mirrors what the plugin sends today (userFragmentOnly) next to what it should send (withBotFragment):
gh api graphql -f query='
{
search(query: "repo:grafana/grafana is:pr author:app/dependabot", type: ISSUE, first: 2) {
nodes {
... on PullRequest {
number
userFragmentOnly: author { ... on User { login databaseId } }
withBotFragment: author { __typename ... on User { login databaseId } ... on Bot { login databaseId } }
}
}
}
}'
Result:
{"number":131544,
"userFragmentOnly":{},
"withBotFragment":{"__typename":"Bot","login":"dependabot","databaseId":49699333}}
End to end:
- Create a GitHub connection with
Use GraphQL APIs enabled, scoped to a repo that receives Dependabot/Renovate/GitHub App PRs.
- Run the blueprint.
SELECT number, author_name, author_id FROM _tool_github_pull_requests WHERE author_name = ''; — every bot-authored PR is listed.
- Repeat with
Use GraphQL APIs disabled: the same PRs come back with dependabot[bot] / 49699333.
Anything else
Happens every time, for every bot-authored PR, on every repo. In our deployment roughly 1,000 PRs opened by our own GitHub App over the last eight months are unattributed, and all 18 bot accounts in _tool_github_accounts (collected from comments and reviews) have zero PRs linked to them.
Three details worth knowing before writing the fix:
1. ... on Bot may only be spread where the schema says Actor. GraphqlInlineAccountQuery is also used for fields typed User — PR and issue assignees, and commit.author.user — and spreading the fragment there makes GitHub reject the entire query, so PR and issue collection would stop working:
gh api graphql -f query='
{ repository(owner:"grafana", name:"grafana") { pullRequest(number:131544) {
assignees(first:1){ nodes { ... on User { login } ... on Bot { login } } } } } }'
{"errors":[{"extensions":{"code":"cannotSpreadFragment","typeName":"Bot","parentName":"User"},
"message":"Fragment on Bot can't be spread inside User"}]}
The Actor fields need their own type, leaving the existing one User-only:
type GithubBotEdge struct {
Login string
Id int `graphql:"databaseId"`
AvatarUrl string
HtmlUrl string `graphql:"url"`
}
// for PullRequest.author, PullRequest.mergedBy, PullRequestReview.author, Issue.author
type GraphqlInlineActorQuery struct {
GithubAccountEdge `graphql:"... on User"`
Bot GithubBotEdge `graphql:"... on Bot"`
}
The named-field-with-inline-fragment-tag pattern is already used in this codebase for RequestedReviewer (#7716), so the client handles it.
2. Bot has a different field set than User. It exposes login, databaseId, avatarUrl, url, createdAt, updatedAt — but no name, company or email, hence the separate struct above. Leaving name, company and email empty matches what the REST collector stores for a bot: GET /users/dependabot[bot] returns null for all three.
3. Bot.login omits the [bot] suffix. GraphQL returns dependabot, REST returns dependabot[bot]; the databaseId is the same 49699333 in both, so there is no risk of duplicate accounts, but the logins would diverge between collectors.
This matters for the is_bot flag added in #9000: the fallback is strings.HasSuffix(githubUser.Login, "[bot]"), and _tool_github_accounts.type is never populated on the GraphQL path (GithubAccountEdge has no Type field, and extractGraphqlPreAccount only writes GithubRepoAccount). So a naive fix would produce bot accounts that is_bot cannot detect. The normalisation should append [bot] to match REST, and ideally set Type: "Bot" on the GithubAccount row.
It also interacts with #8886 / #8894: pr_convertor.go now guards if pr.AuthorId != 0 before generating the account id, so as long as the author stays empty these PRs will keep having no author_id by design — the fix has to happen at collection time.
Existing data cannot be repaired by re-running extract or convert; a Full Refresh re-collect is required, since the author is already empty in _raw_github_graphql_prs.
Same fix applies to mergedBy, which is currently empty for merges performed by a bot.
Version
Verified on main @ 79ef9f4f; the code is unchanged since #8583 (2025-09-24) and is present in v1.0.3-beta16.
Are you willing to submit PR?
Code of Conduct
Search before asking
What happened
When the GitHub connection has
Use GraphQL APIsenabled, every pull request, issue and review opened by an actor of GraphQL typeBot(GitHub Apps, Dependabot, Renovate, GitHub Actions, any AI code-review or automation app) is stored with an empty author:author_name = ''andauthor_id = 0in_tool_github_pull_requests, and nopull_requests.author_idin the domain layer.The data is already empty in the raw layer, so it cannot be recovered by re-running the extractor:
Root cause is in
backend/plugins/github_graphql/tasks/account_graphql_pre_extractor.go:author,mergedBy,assigneesand review authors are all typed asActorin the GitHub schema.Actoris implemented byUser,Bot,Organization,MannequinandEnterpriseUserAccount. The query spreads only... on User, so for any non-Useractor GitHub returns an empty selection set, and theres.Id == 0guard then drops the account entirely.The REST collector is not affected — it resolves bot authors correctly — which is why a connection using GraphQL shows almost no bot-authored PRs while a REST one does.
What do you expect to happen
PRs, issues and reviews opened by GitHub Apps / bots should carry their author, with the same identity the REST collector produces (
dependabot[bot], id49699333), so thataccountsrows,pull_requests.author_idand any bot-aware metric work the same regardless of which collector is used.How to reproduce
The behaviour can be shown against the GitHub API directly, no DevLake instance needed. This query mirrors what the plugin sends today (
userFragmentOnly) next to what it should send (withBotFragment):Result:
{"number":131544, "userFragmentOnly":{}, "withBotFragment":{"__typename":"Bot","login":"dependabot","databaseId":49699333}}End to end:
Use GraphQL APIsenabled, scoped to a repo that receives Dependabot/Renovate/GitHub App PRs.SELECT number, author_name, author_id FROM _tool_github_pull_requests WHERE author_name = '';— every bot-authored PR is listed.Use GraphQL APIsdisabled: the same PRs come back withdependabot[bot]/49699333.Anything else
Happens every time, for every bot-authored PR, on every repo. In our deployment roughly 1,000 PRs opened by our own GitHub App over the last eight months are unattributed, and all 18 bot accounts in
_tool_github_accounts(collected from comments and reviews) have zero PRs linked to them.Three details worth knowing before writing the fix:
1.
... on Botmay only be spread where the schema saysActor.GraphqlInlineAccountQueryis also used for fields typedUser— PR and issue assignees, andcommit.author.user— and spreading the fragment there makes GitHub reject the entire query, so PR and issue collection would stop working:The
Actorfields need their own type, leaving the existing oneUser-only:The named-field-with-inline-fragment-tag pattern is already used in this codebase for
RequestedReviewer(#7716), so the client handles it.2.
Bothas a different field set thanUser. It exposeslogin,databaseId,avatarUrl,url,createdAt,updatedAt— but noname,companyoremail, hence the separate struct above. Leaving name, company and email empty matches what the REST collector stores for a bot:GET /users/dependabot[bot]returnsnullfor all three.3.
Bot.loginomits the[bot]suffix. GraphQL returnsdependabot, REST returnsdependabot[bot]; thedatabaseIdis the same49699333in both, so there is no risk of duplicate accounts, but the logins would diverge between collectors.This matters for the
is_botflag added in #9000: the fallback isstrings.HasSuffix(githubUser.Login, "[bot]"), and_tool_github_accounts.typeis never populated on the GraphQL path (GithubAccountEdgehas noTypefield, andextractGraphqlPreAccountonly writesGithubRepoAccount). So a naive fix would produce bot accounts thatis_botcannot detect. The normalisation should append[bot]to match REST, and ideally setType: "Bot"on theGithubAccountrow.It also interacts with #8886 / #8894:
pr_convertor.gonow guardsif pr.AuthorId != 0before generating the account id, so as long as the author stays empty these PRs will keep having noauthor_idby design — the fix has to happen at collection time.Existing data cannot be repaired by re-running extract or convert; a Full Refresh re-collect is required, since the author is already empty in
_raw_github_graphql_prs.Same fix applies to
mergedBy, which is currently empty for merges performed by a bot.Version
Verified on
main@79ef9f4f; the code is unchanged since #8583 (2025-09-24) and is present inv1.0.3-beta16.Are you willing to submit PR?
Code of Conduct