Skip to content

[Bug][github] GitHub App installation token not refreshed for remote scopes / connection APIs, causing silent "zombie" connection after about 1 hour #8847

Closed

Description

@fxgagnon

Search before asking

  • I had searched in the issues and found no similar issues.

What happened

When using **GitHub App authentication (**not PAT) on a GitHub connection, the connection silently becomes non-functional for administrative operations after approximately 1 hour, even when no requests have been made during that period.

Observed symptoms:

  • The private GitHub organization no longer appears when trying to add a new scope through the Config UI.
  • The API endpoint used to search/list repositories before creating a scope returns empty results, making it impossible to add new scopes.
  • No error is logged in the devlake-lake pod — the failure is completely silent.
  • Data collection on already-configured scopes continues to work normally during the same period. Blueprints run successfully, data is collected, pipelines complete without error.
  • Editing the connection and re-submitting the same GitHub App private key restores the listing functionality for another ~1 hour.
  • Restarting the devlake-lake pod (without re-editing the connection) also restores functionality for ~1 hour.

Key discriminating observation:

Once the connection is in the "zombie" state, REST data collection tasks keep succeeding on existing scopes while the remote scopes listing API returns empty. This decoupling strongly indicates that two different HTTP clients with independent token lifecycles are in use: one is being refreshed correctly, the other is not.

What do you expect to happen

The GitHub App installation token should be transparently refreshed for all HTTP clients used by the GitHub plugin — including the ones backing the remote scopes API (org/repo listing) and the Test Connection endpoint — the same way it is already refreshed for REST data collection tasks since #8746.

The connection should remain fully usable indefinitely without requiring a pod restart or a manual re-edit of the private key.

How to reproduce

  1. Deploy DevLake v1.0.3-beta10 (Helm chart) on Kubernetes.
  2. Create a GitHub connection using GitHub App authentication (App ID + installation ID + private key). Do not use a PAT.
  3. Create a project and add at least one scope pointing to a private repository of the installed organization. Verify that data collection works.
  4. Wait ~1 hour without making any request against the connection.
  5. In the Config UI, attempt to add a new scope to the project: the organization no longer appears in the repository picker.
  6. Via the API, attempt to search repositories through the remote scopes endpoint before creating a scope: the response is empty.
  7. Trigger a blueprint on an existing scope: data collection still succeeds, proving the installation token itself is being refreshed somewhere in the process but not on the admin/listing code path.
  8. Restart the devlake pod (no connection edit, no DB change): listing works again for another ~1 hour.

Anything else

Root cause hypothesis
This appears to be the same architectural pattern as #8788, but on a different code path.

PR #8746 introduced NewAppInstallationTokenProvider + RefreshRoundTripper and wired them into CreateApiClient so that REST data collection tasks get transparent token refresh. Issue #8788 identified that the GraphQL client in backend/plugins/github_graphql/impl/impl.go was not updated as part of #8746 and still uses oauth2.StaticTokenSource, which freezes the token at task start time.

The present bug strongly suggests that the HTTP client(s) backing the remote scopes / connection admin APIs (org listing, repo search, test connection) suffer from the same issue: they were not recabled to the RefreshRoundTripper machinery in #8746. The installation token captured when the process starts (or when the connection is last edited) is cached in memory and never refreshed, so after the 1-hour GitHub installation token TTL expires, all admin API calls fail silently — likely because GitHub returns 401 Bad credentials and the listing code path interprets the empty/errored response as "no orgs available" without surfacing an error.

This would explain every observed characteristic:

  • Why it is silent: the error is swallowed at the listing layer instead of being logged (unlike the GraphQL path in [Bug][github_graphql] GitHub App installation token not refreshed for GraphQL client, panic and pipeline crash on large repos #8788 which panics loudly).
  • Why collection still works: the REST path uses the client correctly refreshed by feat: GitHub App token refresh #8746.
  • Why a pod restart fixes it temporarily: the in-memory frozen token is replaced by a freshly minted one when the process starts.
  • Why re-editing the connection fixes it temporarily: it likely forces a new connection object to be built, including a new client with a fresh token.
  • Why other environments with the same version may not see it: pods that are restarted more frequently (rolling deploys, aggressive liveness probes, autoscaling) never reach the 1-hour threshold in steady state.

Version

v1.0.3-beta10

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Activity

  1. added
    component/pluginsThis issue or PR relates to plugins
    priority/highThis issue is very important
    severity/p1This bug affects functionality or significantly affect ux
    on Apr 23, 2026
  2. fxgagnon commented on Apr 23, 2026

    @fxgagnon
    ContributorAuthor

    I've been able to get debug logs from lake pod when it happens.

    lake time="2026-04-23 11:34:49" level=debug msg="path /plugins/github/connections/1/remote-scopes will continues"
    lake time="2026-04-23 11:34:49" level=debug msg="Authorization is empty"
    lake time="2026-04-23 11:34:49" level=debug msg="user doesn't exist"
    lake time="2026-04-23 11:34:49" level=debug msg="[remote_api_helper] Reused api client"
    lake [GIN] 2026/04/23 - 11:34:49 | 200 | 71.4666858ms | 10.x.x.x | GET "/plugins/github/connections/1/remote-scopes"
    
  3. dosubot commented on Apr 23, 2026

    @dosubot

    Hi @fxgagnon! I'm Dosu and I’m helping the incubator-devlake team.

    Those debug logs confirm the root cause. The "[remote_api_helper] Reused api client" message comes from DsRemoteApiProxyHelper.getApiClient(), which caches API clients in memory keyed by connection.GetHash() (a combination of connection ID + UpdatedAt timestamp). Once a client is cached, it's reused indefinitely — the GitHub App installation token baked into that client is never refreshed.

    The flow when the bug triggers:

    1. First remote-scopes request creates an API client, fetches a fresh installation token, and caches the client.
    2. After ~1 hour the token expires.
    3. Subsequent requests hit the cache ("Reused api client"), send the expired token to GitHub, get a 401, and the response is interpreted as "no orgs/repos" — hence the empty result with a 200 status (no error surfaced).

    The "Authorization is empty" line in your logs is likely from the DevLake auth middleware checking the incoming HTTP request (user → DevLake), not the outgoing GitHub request — it's unrelated to the token issue itself.

    The same RemoteApiHelper.GetApiClient() in remote_api_helper.go has the identical caching pattern without any token expiry check.

    Why collection still works: Task-based data collection creates a fresh API client per task execution via CreateApiClient(taskCtx, connection), so each pipeline run gets a new token.

    Quickest fix path if you're planning to submit a PR: make GetHash() return "" for GitHub App connections (disabling caching and forcing a fresh client + token per request), or add a TTL/expiry check in getApiClient() so cached clients are evicted before the token's ~1 hour lifetime. The Zentao plugin already uses the empty-hash approach as a precedent for connections with expiring credentials.

    To reply, just mention @dosu.


    Share context across your team and agents. Try Dosu.

    Leave Feedback Learn about incubator-devlake with Dosu Add Dosu to your team

  4. added a commit that references this issue on Apr 25, 2026
    f72868c
  5. added a commit that references this issue on Apr 30, 2026
    f103543
  6. added a commit that references this issue on Jul 21, 2026
    c05201d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    component/pluginsThis issue or PR relates to pluginspriority/highThis issue is very importantseverity/p1This bug affects functionality or significantly affect uxtype/bugThis issue is a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions