-
Notifications
You must be signed in to change notification settings - Fork 724
[Bug][github_graphql] GitHub App installation token not refreshed for GraphQL client, panic and pipeline crash on large repos #8788
Description
Search before asking
- I had searched in the issues and found no similar issues.
What happened
When collecting data from a large repository using GitHub App authentication, the pipeline crashes after approximately 1 hour with the following panic:
panic: non-200 OK status code: 401 Unauthorized body: "{
\"message\": \"Bad credentials\",
\"documentation_url\": \"https://docs.github.com/rest\",
\"status\": \"401\"
}"
goroutine 11186 [running]:
github.com/apache/incubator-devlake/helpers/pluginhelper/api.(*GraphqlAsyncClient).updateRateRemaining.func1()
/app/helpers/pluginhelper/api/graphql_async_client.go:129
created by github.com/apache/incubator-devlake/helpers/pluginhelper/api.(*GraphqlAsyncClient).updateRateRemaining
/app/helpers/pluginhelper/api/graphql_async_client.go:117
There are two independent bugs that combine to cause this:
Bug 1: Process crash on any error in the rate-limit goroutine
graphql_async_client.go:129 calls panic(err) inside the background goroutine that periodically polls the GitHub GraphQL rate limit. Any error, including a transient 401, immediately kills the entire DevLake process.
Bug 2: GraphQL HTTP client holds a frozen, never-refreshed token (root cause)
In backend/plugins/github_graphql/impl/impl.go (lines 183-209), the GraphQL HTTP client is built with oauth2.StaticTokenSource:
tokens := strings.Split(connection.Token, ",")
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: tokens[0]},
)
httpClient := oauth2.NewClient(oauthContext, src)This freezes the token to the value at task start time. The client has no connection to the TokenProvider / RefreshRoundTripper machinery that already handles token refresh for the REST client.
By contrast, CreateApiClient (called just above at line 173) correctly wires up NewAppInstallationTokenProvider + RefreshRoundTripper and auto-refreshes on 401. This was added in #8746. The GraphQL client was not updated as part of that PR and still bypasses the refresh infrastructure with its own isolated oauth2 HTTP client.
Since GitHub App installation tokens expire after 1 hour, any pipeline running longer than that will encounter 401s on all GraphQL requests. The rate-limit polling goroutine then hits the 401 and panics, crashing the process.
Note: the documentation page at https://devlake.apache.org/docs/Configuration/GitHub/#github-apps-beta currently states "The GitHub App token expires every hour and does not auto-renew." This was accurate before #8746 but is now stale for the REST path. The GraphQL path is the remaining gap.
What do you expect to happen
- The GitHub App installation token should be transparently refreshed for GraphQL requests, the same way it already is for REST requests.
- If token refresh fails or any other error occurs in the background rate-limit goroutine, the error should be logged and retried, not cause a process-wide panic.
- Pipelines against large repositories should complete successfully without requiring users to fall back to Personal Access Tokens.
How to reproduce
- Configure a GitHub connection using GitHub App authentication (not PAT).
- Create a blueprint targeting a large repository whose full collection takes more than 1 hour.
- Trigger the pipeline.
- After ~1 hour, observe the panic in the logs and the pipeline crashing.
Anything else
Affected files:
| File | Issue |
|---|---|
backend/helpers/pluginhelper/api/graphql_async_client.go:129 |
panic(err) in background goroutine crashes the process on any rate-limit poll error |
backend/plugins/github_graphql/impl/impl.go:183-209 |
oauth2.StaticTokenSource freezes the token at task start; GraphQL client is not wired to TokenProvider / RefreshRoundTripper |
Suggested fix:
-
In
graphql_async_client.go, replacepanic(err)with graceful error handling (log the error and reschedule the next poll) so a transient 401 does not crash the process. -
In
github_graphql/impl/impl.go, instead of creating a newoauth2.StaticTokenSource-backed HTTP client, reuseapiClient.GetClient()-- the REST client's underlying*http.Client-- which already carries theRefreshRoundTripper. This is safe:ApiAsyncClientembeds*ApiClientwhich exposesGetClient(), and proxy is already configured insideNewApiClientFromConnectionso theoauthContextproxy block becomes redundant. Token injection is handled by theRefreshRoundTripper, making theoauth2wrapper unnecessary.
Related: #8746 (added token refresh for REST path)
Version
v1.0.3-beta10
Are you willing to submit PR?
- Yes I am willing to submit a PR!
Code of Conduct
- I agree to follow this project's Code of Conduct