fix: prevent user-input activities from leaking into pending approvals projection#2051
Conversation
…s projection 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'.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Approved This is a straightforward bug fix that adds a guard clause to filter out user-input activities from polluting the pending-approvals projection. The change is small (8 lines), well-documented with comments explaining the intent, and has clear, limited scope - it simply adds an early return for activity kinds that shouldn't create pending-approval rows. You can customize Macroscope's approvability policy. Learn more. |
Integrates upstream/main (d22c6f5) into the fork while preserving all multi-provider support (codex, claudeAgent, copilot, cursor, opencode, geminiCli, amp, kilo) and fork UI/UX additions. Highlights adopted from upstream: - Nightly release channel + update channel selector (pingdotgg#2012, pingdotgg#2049, pingdotgg#1969) - Filesystem browse API + command palette project picker (pingdotgg#2024) - Launch Args setting for Claude provider (pingdotgg#1971) - Kiro editor support in open picker (pingdotgg#1974) - Claude plan events for TodoWrite during input streaming (pingdotgg#1541) - Lost provider session recovery (pingdotgg#1938) - Cache provider status and gate desktop startup (pingdotgg#1962) - LegendList migration for chat scrolling and branch lists (pingdotgg#1953) - Shell snapshot queries + backfill migration (pingdotgg#1973, pingdotgg#2004) - PATH hydration + fallback detection (pingdotgg#1799) - Warm sidebar thread subscriptions (pingdotgg#2001) - Full thread title tooltip (pingdotgg#1994) - Markdown file link UX (pingdotgg#1956), composer polish (pingdotgg#1944, pingdotgg#1992, pingdotgg#1985) - Worktree/branch state + draft reuse fixes (pingdotgg#2005, pingdotgg#2003, pingdotgg#1995, pingdotgg#1936) - Window controls overlay for Windows/Linux (pingdotgg#1969) - Backend readiness timeout 10s→30s (pingdotgg#1979) - Clear tracked RPCs on reconnect, live stream subscriptions (pingdotgg#2000, pingdotgg#1972) - Various misc fixes (pingdotgg#2051, pingdotgg#2052, pingdotgg#2025, pingdotgg#2027, pingdotgg#2049, pingdotgg#1997, pingdotgg#1975) Fork features preserved and reconciled: - All 8 provider adapters + conformance tests - Extended ProviderKind union across contracts/model/settings/provider - appearance/accentColor/themeConfig/ProviderLogo UI system - customModels + gitTextGeneration + providerModelOptions - Migration IDs 23 (NormalizeLegacyProviderKinds) and 24 (RepairProjectionThreadProposedPlanImplementationColumns) kept; new upstream migrations registered at IDs 25-26 to avoid breaking deployed fork databases - DesktopBridge: log directory channels (LOG_DIR/LIST/READ/OPEN_DIR) retained; getWsUrl replaced by upstream's getAppBranding - PROVIDER_CACHE_IDS extended to all 8 providers
Summary
Threads that used user-input prompts (structured questions to the user) permanently display "Pending Approval" in the sidebar even after all inputs have been answered and the turn has completed.
Root cause
applyPendingApprovalsProjectioninProjectionPipeline.tsdoes not filter byactivity.kindbefore creating pending-approval rows. The logic:requestIdfrom any activity payload viaextractActivityRequestIdapproval.resolved→ skipprovider.approval.respond.failed→ skipstatus = "pending"for any remaining activity with arequestIdActivities like
user-input.requestedanduser-input.resolvedcarry arequestIdin their payload, so they fall through to the default branch and create orphan pending-approval rows. Whenuser-input.resolvedarrives, it also falls through and overwrites the row withstatus = "pending"again (since it's notapproval.resolved).These orphan rows are never cleaned up because only
approval.resolvedcan resolve them.Contrast with backfill migration
The backfill migration
024_BackfillProjectionThreadShellSummary.tscorrectly filters bykind = 'approval.requested'when seeding pending-approval rows (line 36). The live projection was missing this same guard.WHERE kind = 'approval.requested'✅requestId❌WHERE kind = 'approval.resolved'✅kind === "approval.resolved"✅WHERE kind = 'provider.approval.respond.failed'✅Fix
Add a
kind !== "approval.requested"guard before the pending-row creation path, so only actual approval requests create entries inprojection_pending_approvals. User-input activities already have their own accounting viaderivePendingUserInputCountFromActivities.Verified with real data
A thread with 11 answered user-input prompts had 11 orphan
pendingrows inprojection_pending_approvals, all withstatus = "pending"andresolved_at = null, even though every single one had a matchinguser-input.resolvedevent in the event store. The thread's session wasstatus = "ready"withactive_turn_id = null.Test plan
ProjectionPipeline.test.tstests passNote
Low Risk
Small, localized change to projection logic that narrows when pending-approval rows are created; risk is limited to approval sidebar/count behavior.
Overview
Fixes an issue where
user-input.*activities that include arequestIdcould incorrectly create/overwriteprojection_pending_approvalsrows, leaving threads stuck showing Pending Approval.applyPendingApprovalsProjectionnow only createsstatus="pending"rows foractivity.kind === "approval.requested", while keeping existing resolve/stale-failure handling intact.Reviewed by Cursor Bugbot for commit 51ea2b1. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix pending approvals projection to only process
approval.requestedactivitiesPreviously, any activity event carrying a
requestIdcould upsert a row into the pending-approval repository, causing user-input activities to leak into the pending approvals projection. A guard is added inProjectionPipeline.tsthat returns early unlessevent.payload.activity.kind === 'approval.requested'.Macroscope summarized 51ea2b1.