Skip to content

fix(github): getRepositoryCollaboratorPermission/getGithubUserCreatedAt skip the installation-token self-heal every sibling call uses #9315

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) and #8892 (fix(github): read-path GitHub calls in backfill.ts/pr-freshness.ts never self-heal a stale cached installation token) are the two prior instances of this exact bug class in this same directory — both closed
as completed. This issue is the same fix applied to the two remaining call sites that never
adopted it.

withInstallationTokenRetry (src/github/app.ts:180-206) exists specifically to evict and
re-mint a stale cached installation token on a bad-credentials/permission-scope error, retrying
the operation exactly once with a freshly-minted token instead of surfacing the transient failure.
Every GitHub write helper in this directory routes through it — assignees.ts:40, labels.ts:33,88,
comments.ts:145, issues.ts:51, milestones.ts:29,48, e2e-test-commit.ts:62,
pr-actions.ts (11 call sites), repo-doc-pr.ts:170 — per the #6191 convention.

Two functions in src/github/app.ts itself never adopted it, calling createInstallationToken
directly and issuing a raw timeoutFetch with no retry:

  • getRepositoryCollaboratorPermission (src/github/app.ts:477-497): throws on any non-OK
    response, including a 401 from a stale token.
  • getGithubUserCreatedAt (src/github/app.ts:520-534): fails open (return null) on any
    non-OK response — so a stale-token 401 here silently disables the check rather than throwing,
    but still never self-heals.

getRepositoryCollaboratorPermission in particular is this codebase's sole "real GitHub
permission" trust oracle. It is used, directly or via a thin wrapper, by:

  • requireRepoWriteAccess in src/api/routes.ts (documented: "Fails closed (403) if write
    access can't be verified"
    )
  • isPerTenantAdmin in src/auth/security.ts (documented: "Fail-CLOSED: no installation to ask
    through, a lookup error... all deny"
    )
  • command/write authorization and trusted-reviewer filtering in src/queue/processors.ts
  • maintainer-permission checks in src/queue/review-evasion.ts

Because every one of those call sites is explicitly fail-closed, a transient stale-token 401 on
this one read currently causes a legitimate maintainer's write-access check to fail, a real
changes_requested review to be filtered out as "untrusted", or a genuine collaborator's command
to be denied — for exactly the transient-token-staleness scenario withInstallationTokenRetry
already exists to absorb everywhere else in this file.

Requirements

  • getRepositoryCollaboratorPermission must route its GitHub call through
    withInstallationTokenRetry, matching the pattern every other write/read helper in this
    directory already uses (see e.g. src/github/issues.ts:51 for the shape of wrapping a single
    fetch call in it).
  • getGithubUserCreatedAt must likewise route its GitHub call through withInstallationTokenRetry.
  • Each function's existing failure contract must be preserved exactly for every case other than a
    self-healed retry: getRepositoryCollaboratorPermission still returns null on a 404 and still
    throws on a genuine (non-token) non-OK response; getGithubUserCreatedAt still fail-opens
    (return null) on any non-OK response or thrown error that is not a self-healable
    bad-credentials/permission-scope error.
  • Do not introduce a new shared helper beyond withInstallationTokenRetry itself (already
    exported from this same file) — this is a direct application of an existing, already-tested
    convention, not a new abstraction.

Deliverables

  • getRepositoryCollaboratorPermission (src/github/app.ts:477) wraps its GitHub call in
    withInstallationTokenRetry.
  • getGithubUserCreatedAt (src/github/app.ts:520) wraps its GitHub call in
    withInstallationTokenRetry.
  • Regression test in test/unit/github-app.test.ts for each function, asserting a
    bad-credentials/401 on the first attempt is retried once with a freshly-minted token and
    succeeds, rather than throwing (getRepositoryCollaboratorPermission) or fail-opening to
    null (getGithubUserCreatedAt) on the first attempt — mirroring the existing
    withInstallationTokenRetry regression-test pattern already used for other call sites in
    this same test file (e.g. the existing tests referenced by #6191/#8892).
  • Existing tests for the 404 (getRepositoryCollaboratorPermission) and generic-failure
    (getGithubUserCreatedAt) cases continue to pass unmodified in behavior.

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/github/app.ts — both
the new retry-wrapped branch and every existing branch (404, non-token failure, fail-open) must be
exercised.

Expected Outcome

A stale cached installation token on either of these two reads self-heals via one retry, exactly
matching the existing convention every other GitHub call in this directory follows (#6191,
#8892), instead of causing a fail-closed authorization check to wrongly deny a legitimate
maintainer/collaborator, or an anti-abuse check to silently no-op, on a transient token issue.

Links & Resources

  • src/github/app.ts:180-206 (withInstallationTokenRetry, the helper to use)
  • src/github/app.ts:477-497 (getRepositoryCollaboratorPermission, fix feat(docs): add install site and mcp diagnostics #1)
  • src/github/app.ts:520-534 (getGithubUserCreatedAt, fix chore(release): prepare public gittensory launch #2)
  • #6191, #8892 (the two prior, closed instances of this exact bug class in this directory)
  • src/api/routes.ts (requireRepoWriteAccess), src/auth/security.ts (isPerTenantAdmin) — the
    fail-closed consumers this fix protects
  • test/unit/github-app.test.ts (existing, incomplete coverage for these two functions)

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