Skip to content

fix(integrations): GitHubMilestonesAdapter/GitHubProjectsAdapter never self-heal a stale installation token #9316

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

Mirror this fix exactly: #6191 (fix(github): label/assignee writes skip the installation-token retry every sibling GitHub-write helper uses) established the
withInstallationTokenRetry convention for every GitHub write helper in src/github/. This
issue applies the identical fix to src/integrations/project-tracker-adapter.ts, which never
adopted it.

withInstallationTokenRetry (src/github/app.ts:190-206) evicts and re-mints a stale cached
installation token on a bad-credentials/permission-scope error, retrying the wrapped operation
once with a freshly-minted token. Every GitHub-write helper in src/github/ routes its
installation-token GitHub calls through it (assignees.ts:40, labels.ts:33,88,
comments.ts:145, issues.ts:51, milestones.ts:29,48, pr-actions.ts, repo-doc-pr.ts:170).

GitHubMilestonesAdapter and GitHubProjectsAdapter in src/integrations/project-tracker-adapter.ts
implement the same ProjectTrackerAdapter interface as a GitHub-backed tracker, and their four
real (non-inert) methods each call createInstallationToken directly and pass the token straight
to makeInstallationOctokit, with no retry wrapper anywhere in this file:

  • GitHubMilestonesAdapter.listOpenMilestones (src/integrations/project-tracker-adapter.ts:79-95)
  • GitHubMilestonesAdapter.attachToMilestone (:104-116, a write — patches the PR's milestone)
  • GitHubProjectsAdapter.listOpenProjects (:200-226)
  • GitHubProjectsAdapter.attachToProject (:235-249, a write — adds the PR to a Projects v2
    board)

makeInstallationOctokit (src/github/client.ts:708-742) only wraps requests in a
write-suppression hook for self-hosted dry-run/paused modes and routes fetches through
timeoutFetch for caching/rate-limit-retry purposes (fetchWithGitHubRetry) — neither of those
mechanisms retries a bad-credentials (401) response; that is specifically
withInstallationTokenRetry's job, and it is never invoked anywhere in this file (confirmed: zero
references to withInstallationTokenRetry in src/integrations/project-tracker-adapter.ts). A
stale cached installation token on any of these four calls throws straight through to the caller
(resolveTrackerMatches's .catch(() => []) in the list case degrades this to "no suggestion";
the two attach methods propagate the failure to whatever action executed the attach) instead of
self-healing the way the equivalent GitHub write in src/github/ would.

Requirements

  • All four call sites listed above (listOpenMilestones, attachToMilestone, listOpenProjects,
    attachToProject) must route their createInstallationToken + Octokit call sequence through
    withInstallationTokenRetry (imported from src/github/app.ts), matching the pattern already
    used throughout src/github/.
  • Preserve each method's existing return contract and existing non-token error handling exactly —
    this issue only adds the self-heal-on-stale-token retry, it does not change any other behavior
    (pagination, the projectId/milestoneId validation guards, the inert stub methods, etc.).
  • Follow this repo's established convention (see withInstallationTokenRetry's own usage
    elsewhere) of wrapping the whole operation — token mint through the Octokit call(s) that use it —
    in the retry, not just the token mint itself.

Deliverables

  • GitHubMilestonesAdapter.listOpenMilestones wraps its token+Octokit sequence in
    withInstallationTokenRetry.
  • GitHubMilestonesAdapter.attachToMilestone wraps its token+Octokit sequence in
    withInstallationTokenRetry.
  • GitHubProjectsAdapter.listOpenProjects wraps its token+Octokit sequence in
    withInstallationTokenRetry.
  • GitHubProjectsAdapter.attachToProject wraps its token+Octokit sequence in
    withInstallationTokenRetry.
  • Regression tests in test/unit/project-tracker-adapter.test.ts for all four methods,
    asserting a bad-credentials/401 on the first Octokit call is retried once with a
    freshly-minted token and succeeds, rather than throwing/propagating on the first attempt.

All of the above Deliverables are required in the same PR.

Test Coverage Requirements

99%+ Codecov patch coverage, branch-counted, on every changed line in
src/integrations/project-tracker-adapter.ts.

Expected Outcome

A stale cached installation token on any of the four GitHub-backed ProjectTrackerAdapter methods
self-heals via one retry, matching the convention every GitHub write/read call in src/github/
already follows (#6191), instead of a transient token issue silently degrading to "no
suggestion" (list methods) or failing an attach outright (write methods).

Links & Resources

  • src/github/app.ts:190-206 (withInstallationTokenRetry, the helper to use)
  • src/integrations/project-tracker-adapter.ts:79-95, :104-116, :200-226, :235-249 (the four
    call sites)
  • #6191 (the prior, closed instance of this exact bug class for src/github/)
  • test/unit/project-tracker-adapter.test.ts (existing coverage — does not exercise a
    stale-token/401 case for any of these four methods)

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions