diff --git a/src/github/backfill.ts b/src/github/backfill.ts index 1844ca2582..203db9565b 100644 --- a/src/github/backfill.ts +++ b/src/github/backfill.ts @@ -1193,7 +1193,12 @@ async function refreshInstallationHealthRecords(env: Env, installations: Install const installedRepos = repositories.filter((repo) => repo.installationId === currentInstallation.id && repo.isInstalled); const registeredInstalled = installedRepos.filter((repo) => repo.isRegistered); const installedSettings = await Promise.all(installedRepos.map((repo) => resolveRepositorySettings(env, repo.fullName))); - const requiresChecks = installedSettings.some((settings) => settings.checkRunMode === "enabled"); + // #5355: also require Checks: write when the review-agent check-run (reviewCheckMode) publishes, not + // just when the separate context check (checkRunMode) is enabled -- buildInstallationRepairDiagnostics + // above already ORs both (checkRunRepoCount / gateCheckRepoCount); this persisted health record was + // missing the second arm, so an installation with only reviewCheckMode set never got flagged for the + // Checks permission it actually needs. + const requiresChecks = installedSettings.some((settings) => settings.checkRunMode === "enabled" || shouldPublishReviewCheck(settings.reviewCheckMode)); const requiresPrWrite = installedSettings.some((settings) => agentRequiresPrWrite(settings.autonomy)); const requiresContentsWrite = installedSettings.some((settings) => agentRequiresContentsWrite(settings.autonomy)); const requiredPermissions = { diff --git a/test/unit/backfill.test.ts b/test/unit/backfill.test.ts index d348398669..e408f432dc 100644 --- a/test/unit/backfill.test.ts +++ b/test/unit/backfill.test.ts @@ -1020,6 +1020,55 @@ describe("GitHub backfill", () => { ); }); + it("REGRESSION (#5355): requires Checks write for a repo with only reviewCheckMode (the Orb Review Agent check) set, not checkRunMode", async () => { + // Before the fix, requiresChecks only looked at checkRunMode ("Gittensory Context" check) and missed + // the separate reviewCheckMode axis ("Gittensory Orb Review Agent" check) entirely -- so an installation + // whose repos only ever published the review-agent check (true for JSONbored's own 3 production repos, + // none of which set checkRunMode) was never flagged as needing the Checks permission. + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); + await seedRegisteredRepo(env); + await upsertInstallation(env, { + installation: { + id: 123, + account: { login: "JSONbored", id: 1, type: "User" }, + repository_selection: "selected", + permissions: { metadata: "read", pull_requests: "write", issues: "write" }, + events: ["issues", "issue_comment", "pull_request", "repository", "installation_repositories"], + }, + }); + await upsertRepositoryFromGitHub(env, { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, 123); + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + reviewCheckMode: "required", // checkRunMode left at its default ("off") + }); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.endsWith("/app/installations/123")) { + return Response.json({ + id: 123, + account: { login: "JSONbored", id: 1, type: "User" }, + repository_selection: "selected", + permissions: { metadata: "read", pull_requests: "write", issues: "write" }, + events: ["issues", "issue_comment", "pull_request", "repository", "installation_repositories"], + }); + } + return new Response("not found", { status: 404 }); + }); + + const refreshed = await refreshInstallationHealth(env); + + expect(refreshed.installations).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + installationId: 123, + status: "needs_attention", // was falsely "healthy" before the fix + missingPermissions: ["checks"], + requiredPermissions: expect.objectContaining({ checks: "write" }), + }), + ]), + ); + }); + it("REGRESSION (#audit-install-health): an acting autonomy requires pull_requests:write, so read-only is needs_attention not healthy", async () => { const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); await seedRegisteredRepo(env);