fix(connectors): stop GitHub access refreshes from advancing agent runtimes (#19902)

This commit is contained in:
Nicky Leach authored and GitHub committed 2026-11-07 14:43:39 -08:00
1 parent eb7c7b083a
commit 0363ebec70
4 files changed
+165 -2

No files matched your search

@@ -6,8 +6,8 @@ import { randomUUID, generateKeyPairSync, verify, createHash, diffieHellman, hkd
import { createServer, type Server } from "node:http";
import { once } from "node:events";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { eq, sql } from "drizzle-orm";
import { applyPendingMigrations, companies, activityLog, companySecrets, connectionEventDeliveries, connectionGrants, createDb, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, toolApplications, toolConnections } from "@paperclipai/db";
import { and, eq, sql } from "drizzle-orm";
import { applyPendingMigrations, agents, companies, activityLog, companySecrets, connectionEventDeliveries, connectionGrants, createDb, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, resourceLifecycleEvents, toolApplications, toolConnections } from "@paperclipai/db";
import { resolveExternalTestDatabaseUrl } from "./helpers/external-test-database.js";
import { createGitHubGrantContinuity, githubGrantContinuityDeadline, githubContinuityRevision } from "../services/tool-access/github-grant-continuity.js";
import { createPaperclipCloudConnector } from "../services/paperclip-cloud-connector.js";
@@ -174,6 +174,32 @@ describeDatabase("GitHub coordinator with PostgreSQL and controlled Cloud HTTP (
} finally { await ew.stop(); await cw.stop(); await coordinator.stop(); }
});
it("rereads unchanged access on a scheduled refresh without advancing agent runtime revisions", async () => {
const f = await fixture(["101"]);
const agentId = randomUUID();
await db.insert(agents).values({ id: agentId, companyId: f.company.id, name: "Runtime fixture", adapterType: "paperclip_runner" });
vi.spyOn(globalThis, "fetch").mockImplementation((url, init) => String(url).startsWith("https://api.github.com")
? realFetch(`${origin}${new URL(String(url)).pathname}${new URL(String(url)).search}`, init) : realFetch(url, init));
const actor = { actorType: "system", actorId: "tool_health_sweep" } as const;
const runtimeEvents = () => db.select().from(resourceLifecycleEvents).where(and(
eq(resourceLifecycleEvents.companyId, f.company.id), eq(resourceLifecycleEvents.resourceType, "agent_runtime")));
const grant = async () => (await db.select().from(connectionGrants).where(eq(connectionGrants.id, f.grant.id)))[0]!;
try {
// The first refresh records the repository list the fixture lacked.
await f.service.refreshManagedGitHubGrantAccess(f.connection, f.grant, actor);
const first = await grant();
const before = await runtimeEvents();
expect(before.length).toBeGreaterThan(0);
await f.service.refreshManagedGitHubGrantAccess(f.connection, first, actor);
const second = await grant();
expect(second.providerTenant!.github!.accessRevision).not.toBe(first.providerTenant!.github!.accessRevision);
expect(second.providerTenant!.github!.bindingSync).toBeUndefined();
expect(bindings.filter(binding => binding.active === true)).toHaveLength(2);
expect(await runtimeEvents()).toEqual(before);
} finally {
await db.delete(agents).where(eq(agents.id, agentId));
}
});
it("failed binding removal persists retry/debt through restart, uses current lease deadline and settles without refetching GitHub metadata", async () => {
const f = await fixture(["101"]); fakeClock();
await db.update(connectionGrants).set({ providerTenant: { ...f.grant.providerTenant, github: { ...f.grant.providerTenant!.github!,