You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ 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)
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 closedas 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 andre-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#6191convention.Two functions in
src/github/app.tsitself never adopted it, callingcreateInstallationTokendirectly and issuing a raw
timeoutFetchwith no retry:getRepositoryCollaboratorPermission(src/github/app.ts:477-497): throws on any non-OKresponse, including a 401 from a stale token.
getGithubUserCreatedAt(src/github/app.ts:520-534): fails open (return null) on anynon-OK response — so a stale-token 401 here silently disables the check rather than throwing,
but still never self-heals.
getRepositoryCollaboratorPermissionin particular is this codebase's sole "real GitHubpermission" trust oracle. It is used, directly or via a thin wrapper, by:
requireRepoWriteAccessinsrc/api/routes.ts(documented: "Fails closed (403) if writeaccess can't be verified")
isPerTenantAdmininsrc/auth/security.ts(documented: "Fail-CLOSED: no installation to askthrough, a lookup error... all deny")
src/queue/processors.tssrc/queue/review-evasion.tsBecause 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_requestedreview to be filtered out as "untrusted", or a genuine collaborator's commandto be denied — for exactly the transient-token-staleness scenario
withInstallationTokenRetryalready exists to absorb everywhere else in this file.
Requirements
getRepositoryCollaboratorPermissionmust route its GitHub call throughwithInstallationTokenRetry, matching the pattern every other write/read helper in thisdirectory already uses (see e.g.
src/github/issues.ts:51for the shape of wrapping a singlefetch call in it).
getGithubUserCreatedAtmust likewise route its GitHub call throughwithInstallationTokenRetry.self-healed retry:
getRepositoryCollaboratorPermissionstill returnsnullon a 404 and stillthrows on a genuine (non-token) non-OK response;
getGithubUserCreatedAtstill fail-opens(
return null) on any non-OK response or thrown error that is not a self-healablebad-credentials/permission-scope error.
withInstallationTokenRetryitself (alreadyexported 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 inwithInstallationTokenRetry.getGithubUserCreatedAt(src/github/app.ts:520) wraps its GitHub call inwithInstallationTokenRetry.test/unit/github-app.test.tsfor each function, asserting abad-credentials/401 on the first attempt is retried once with a freshly-minted token and
succeeds, rather than throwing (
getRepositoryCollaboratorPermission) or fail-opening tonull(getGithubUserCreatedAt) on the first attempt — mirroring the existingwithInstallationTokenRetryregression-test pattern already used for other call sites inthis same test file (e.g. the existing tests referenced by
#6191/#8892).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— boththe 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 legitimatemaintainer/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) — thefail-closed consumers this fix protects
test/unit/github-app.test.ts(existing, incomplete coverage for these two functions)