From 7d8e1945da3fe9eda62ad2b3d719e4406de49e32 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 1 Apr 2026 13:21:58 +0000 Subject: [PATCH 1/6] Generated with Hive: Fix CI by setting dummy database URL for dependency installation --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d0bb521f26..edc748ae35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,8 @@ jobs: - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' + env: + DATABASE_URL: postgresql://dummy:dummy@localhost:5432/dummy run: npm ci - name: Run unit tests From ff51d3eca65a44be4e9e0d923afaa21407394160 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 1 Apr 2026 13:37:25 +0000 Subject: [PATCH 2/6] Generated with Hive: Fix migration failure by adding column with if not exists safeguard --- .../20260301004827_add_fast_track_to_feature/migration.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/migrations/20260301004827_add_fast_track_to_feature/migration.sql b/prisma/migrations/20260301004827_add_fast_track_to_feature/migration.sql index 68c409bef3..455ce7c9f3 100644 --- a/prisma/migrations/20260301004827_add_fast_track_to_feature/migration.sql +++ b/prisma/migrations/20260301004827_add_fast_track_to_feature/migration.sql @@ -1,2 +1,2 @@ -- AlterTable -ALTER TABLE "features" ADD COLUMN "is_fast_track" BOOLEAN NOT NULL DEFAULT false; +ALTER TABLE "features" ADD COLUMN IF NOT EXISTS "is_fast_track" BOOLEAN NOT NULL DEFAULT false; From 5238c0bbb5f3bd79b1718e277a9e731318e62412 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 1 Apr 2026 13:47:13 +0000 Subject: [PATCH 3/6] Generated with Hive: Resolve failed Prisma migration for fast track feature in deploy workflow --- .github/workflows/deployPR.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deployPR.yml b/.github/workflows/deployPR.yml index ac9cf53c4e..508a6ad1eb 100644 --- a/.github/workflows/deployPR.yml +++ b/.github/workflows/deployPR.yml @@ -329,6 +329,10 @@ jobs: "20260228185557_default_ticket_sweep_enabled" \ "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='janitor_configs' AND column_name='ticket_sweep_enabled' AND column_default='true';" + resolve_migration \ + "20260301004827_add_fast_track_to_feature" \ + "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='features' AND column_name='is_fast_track';" + - name: Build run: | export DATABASE_URL="${{ steps.neon.outputs.connection_uri }}" From 17774736209b13ff429d992c7634c06ab6dd43b1 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 1 Apr 2026 13:59:00 +0000 Subject: [PATCH 4/6] Generated with Hive: Resolve stuck Prisma migration in deploy preview workflow --- .github/workflows/deployPR.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/deployPR.yml b/.github/workflows/deployPR.yml index 508a6ad1eb..9224c15b7f 100644 --- a/.github/workflows/deployPR.yml +++ b/.github/workflows/deployPR.yml @@ -339,6 +339,15 @@ jobs: echo "DATABASE_URL is set (masked)" npx prisma generate + # Resolve any known stuck migrations before attempting deploy + # (idempotent — safe to run even if migration is not stuck) + resolve_stuck() { + local name="$1" + echo "Resolving potentially stuck migration: $name" + npx prisma migrate resolve --rolled-back "$name" 2>/dev/null || true + } + resolve_stuck "20260301004827_add_fast_track_to_feature" + # Retry prisma migrate deploy — Neon endpoint may sleep between steps for attempt in 1 2 3 4 5; do if npx prisma migrate deploy; then @@ -350,6 +359,8 @@ jobs: exit 1 fi echo "⚠️ Migration attempt $attempt failed, waiting 15s before retry..." + # Re-attempt resolution in case of transient failures + resolve_stuck "20260301004827_add_fast_track_to_feature" sleep 15 done From fd90123a3ba566ac2f0d6b91631633727057bfca Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Tue, 14 Apr 2026 10:56:47 +0000 Subject: [PATCH 5/6] Generated with Hive: Fix PR stats test by using deterministic artifact bucket IDs --- src/__tests__/integration/api/admin/pr-stats.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__tests__/integration/api/admin/pr-stats.test.ts b/src/__tests__/integration/api/admin/pr-stats.test.ts index 7d4db52b7c..c325e024d1 100644 --- a/src/__tests__/integration/api/admin/pr-stats.test.ts +++ b/src/__tests__/integration/api/admin/pr-stats.test.ts @@ -104,7 +104,7 @@ describe("GET /api/admin/workspaces/[id]/pr-stats (integration)", () => { messageId: message.id, type: "PULL_REQUEST", content: { - url: `https://github.com/testorg/testrepo/pull/${Math.floor(Math.random() * 9999)}`, + url: `https://github.com/testorg/testrepo/pull/${ageHours * 100}`, repo: "testorg/testrepo", status, title: `Test PR (${status})`, @@ -144,6 +144,7 @@ describe("GET /api/admin/workspaces/[id]/pr-stats (integration)", () => { repositoryUrl: "https://github.com/testorg/bucketrepo", }); + let artifactCounter = 1; async function seedDoneArtifact(ageHours: number) { const task = await createTestTask({ workspaceId: workspace.id, createdById: regularUser.id }); const message = await createTestChatMessage({ taskId: task.id, message: "test" }); @@ -153,7 +154,7 @@ describe("GET /api/admin/workspaces/[id]/pr-stats (integration)", () => { messageId: message.id, type: "PULL_REQUEST", content: { - url: `https://github.com/testorg/bucketrepo/pull/${Math.floor(Math.random() * 9999)}`, + url: `https://github.com/testorg/bucketrepo/pull/${artifactCounter++}`, repo: "testorg/bucketrepo", status: "DONE", title: "Test PR", From aa0220133e3a2b07f2b0c02620be28c204f89724 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 15 Apr 2026 19:21:39 +0000 Subject: [PATCH 6/6] Generated with Hive: Fix deferred notification creation to set sendAfter atomically --- .../unit/services/notifications.test.ts | 6 +++--- src/services/notifications.ts | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/__tests__/unit/services/notifications.test.ts b/src/__tests__/unit/services/notifications.test.ts index 548f245a01..2fe9293901 100644 --- a/src/__tests__/unit/services/notifications.test.ts +++ b/src/__tests__/unit/services/notifications.test.ts @@ -182,16 +182,16 @@ describe("createAndSendNotification", () => { await createAndSendNotification(baseInput); expect(create).toHaveBeenCalledOnce(); - expect(mockedSendDirectMessage).not.toHaveBeenCalled(); - expect(update).toHaveBeenCalledWith( + expect(create).toHaveBeenCalledWith( expect.objectContaining({ - where: { id: "notif-1" }, data: expect.objectContaining({ sendAfter: expect.any(Date), message: baseInput.message, }), }) ); + expect(mockedSendDirectMessage).not.toHaveBeenCalled(); + expect(update).not.toHaveBeenCalled(); }); }); diff --git a/src/services/notifications.ts b/src/services/notifications.ts index eeb3c6bb4e..3954d381e3 100644 --- a/src/services/notifications.ts +++ b/src/services/notifications.ts @@ -75,7 +75,11 @@ export async function createAndSendNotification(input: { : null; const dmReady = isDirectMessageConfigured() && !!decryptedPubkey; - // 4. Always insert a row — use SKIPPED when DM is not ready + // 4. Compute deferred fields up-front to avoid a two-step create+update race + const isDeferred = dmReady && DEFERRED_NOTIFICATION_TYPES.has(input.notificationType); + const sendAfter = isDeferred ? new Date(Date.now() + DEFERRED_DELAY_MS) : null; + + // 5. Insert a single row with all fields set atomically const record = await db.notificationTrigger.create({ data: { targetUserId: input.targetUserId, @@ -88,10 +92,11 @@ export async function createAndSendNotification(input: { : NotificationTriggerStatus.SKIPPED, notificationMethod: NotificationMethod.SPHINX, notificationTimestamps: [], + ...(isDeferred && { sendAfter, message: input.message }), }, }); - // 5. Stop here if DM is not configured — no send attempted + // 6. Stop here if DM is not configured — no send attempted if (!dmReady) { logger.info( `[Notifications] DM not ready — record created as SKIPPED for ${input.notificationType}`, @@ -101,15 +106,10 @@ export async function createAndSendNotification(input: { return; } - // 6. Deferred types: store sendAfter + message, return without sending - if (DEFERRED_NOTIFICATION_TYPES.has(input.notificationType)) { - const sendAfter = new Date(Date.now() + DEFERRED_DELAY_MS); - await db.notificationTrigger.update({ - where: { id: record.id }, - data: { sendAfter, message: input.message }, - }); + // 7. Deferred types: log and return — sendAfter already persisted above + if (isDeferred) { logger.info( - `[Notifications] Deferred ${input.notificationType} — will dispatch after ${sendAfter.toISOString()}`, + `[Notifications] Deferred ${input.notificationType} — will dispatch after ${sendAfter!.toISOString()}`, "NOTIFICATIONS", { recordId: record.id, targetUserId: input.targetUserId, taskId, featureId } );