From 240ce2a6736b03da0d5b80468734b6215fcf635e Mon Sep 17 00:00:00 2001 From: enjoyandlove Date: Tue, 19 May 2026 10:12:50 -0400 Subject: [PATCH 1/3] fix(installation): replace find-then-insert with atomic upsert (#110) --- .../webhook/handlers/installation.handler.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/das/src/webhook/handlers/installation.handler.ts b/packages/das/src/webhook/handlers/installation.handler.ts index 2638652..7eaf630 100644 --- a/packages/das/src/webhook/handlers/installation.handler.ts +++ b/packages/das/src/webhook/handlers/installation.handler.ts @@ -38,22 +38,19 @@ export class InstallationHandler { payload.repositories ?? payload.repositories_added ?? []; for (const repo of repos) { - // Check existence first so we only set added_at on insert, not on every - // re-fire of installation.created / installation_repositories.added. - const existing = await this.repoRepo.findOneBy({ - repoFullName: repo.full_name, - }); - if (existing) { - await this.repoRepo.update(repo.full_name, { - installationId: String(installationId), - }); - } else { - await this.repoRepo.insert({ + // Atomic upsert: insert with addedAt on first encounter; on conflict only + // update installationId so addedAt is never overwritten on re-fires. + await this.repoRepo + .createQueryBuilder() + .insert() + .into(Repo) + .values({ repoFullName: repo.full_name, installationId: String(installationId), addedAt: new Date().toISOString(), - }); - } + }) + .orUpdate(["installationId"], ["repoFullName"]) + .execute(); this.logger.log(`Tracking repo: ${repo.full_name}`); } From c263954f4f92ae1876f33c49398030337de37228 Mon Sep 17 00:00:00 2001 From: enjoyandlove Date: Thu, 21 May 2026 15:43:37 -0400 Subject: [PATCH 2/3] fix(installation): normalize repo name to lowercase on add and use case-insensitive match on remove --- .../src/webhook/handlers/installation.handler.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/das/src/webhook/handlers/installation.handler.ts b/packages/das/src/webhook/handlers/installation.handler.ts index 7eaf630..3a54c97 100644 --- a/packages/das/src/webhook/handlers/installation.handler.ts +++ b/packages/das/src/webhook/handlers/installation.handler.ts @@ -45,7 +45,7 @@ export class InstallationHandler { .insert() .into(Repo) .values({ - repoFullName: repo.full_name, + repoFullName: (repo.full_name as string).toLowerCase(), installationId: String(installationId), addedAt: new Date().toISOString(), }) @@ -55,12 +55,17 @@ export class InstallationHandler { } // installation_repositories.removed — soft clear, preserve historical data. + // Use LOWER() to handle rows stored with any casing (admin API vs webhook paths). const removed: any[] = payload.repositories_removed ?? []; for (const repo of removed) { - await this.repoRepo.update(repo.full_name, { - installationId: null, - registered: false, - }); + await this.repoRepo + .createQueryBuilder() + .update() + .set({ installationId: null, registered: false }) + .where("LOWER(repo_full_name) = LOWER(:name)", { + name: repo.full_name, + }) + .execute(); this.logger.log(`Stopped tracking repo: ${repo.full_name}`); } } From 89c598f505c40ed8819ef415f72f3eb1a7a43033 Mon Sep 17 00:00:00 2001 From: enjoyandlove Date: Thu, 21 May 2026 16:08:07 -0400 Subject: [PATCH 3/3] fix(installation): drop duplicate repoFullName key breaking lowercase normalization --- packages/das/src/webhook/handlers/installation.handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/das/src/webhook/handlers/installation.handler.ts b/packages/das/src/webhook/handlers/installation.handler.ts index c6585ce..3a54c97 100644 --- a/packages/das/src/webhook/handlers/installation.handler.ts +++ b/packages/das/src/webhook/handlers/installation.handler.ts @@ -46,7 +46,6 @@ export class InstallationHandler { .into(Repo) .values({ repoFullName: (repo.full_name as string).toLowerCase(), - repoFullName: repo.full_name, installationId: String(installationId), addedAt: new Date().toISOString(), })