From 51ea2b1f6624617564969f822a1a4986974ccae1 Mon Sep 17 00:00:00 2001 From: Marcus Grando Date: Wed, 15 Apr 2026 22:49:32 -0300 Subject: [PATCH] fix: prevent user-input activities from leaking into pending approvals projection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activities with a requestId in their payload (e.g. user-input.requested, user-input.resolved) were incorrectly creating rows in the projection_pending_approvals table. The fall-through default branch in applyPendingApprovalsProjection only checked for approval.resolved and provider.approval.respond.failed before unconditionally upserting a "pending" row for any remaining activity carrying a requestId. This caused threads with answered user-input prompts to permanently display "Pending Approval" in the sidebar, since user-input.resolved never clears the erroneous pending row — only approval.resolved does. Add a kind guard (approval.requested) before the pending-row creation path, consistent with the backfill migration 024 which already filters by kind = 'approval.requested'. --- .../server/src/orchestration/Layers/ProjectionPipeline.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index aa1109a4d1d..d981ae0da62 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -1284,6 +1284,14 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti } return; } + // Only approval-requested activities should create pending-approval + // rows. Other activity kinds that happen to carry a requestId + // (e.g. user-input.requested / user-input.resolved) must not + // pollute this projection — they have their own accounting via + // derivePendingUserInputCountFromActivities. + if (event.payload.activity.kind !== "approval.requested") { + return; + } if (Option.isSome(existingRow) && existingRow.value.status === "resolved") { return; }