Comprehensive workflow reminder management
calcom/cal.com#7232Golden Comments (2/2 found)
| # | Comment | Severity | Status | FriendlyReviewer Detail |
|---|---|---|---|---|
| #1 | Async functions deleteScheduledEmailReminder and deleteScheduledSMSReminder called without await in forEach → fire-and-forget, reminders not reliably cancelled |
HIGH | ✓ Found | FriendlyReviewer found this bug in 4 files (6 occurrences): handleCancelBooking.ts (L483), bookings.tsx (L488), workflows.tsx (L211, 375, 521, 576). Also identified duplicate processing of reminders during reschedule in handleNewBooking.ts (L961). Each instance classified as HIGH with a recommendation to replace forEach(async) with for...of or Promise.all. |
| #2 | immediateDelete=true: SendGrid email cancelled but DB record not deleted/marked cancelled → orphaned records |
HIGH | ✓ Found | FriendlyReviewer precisely identified this bug in handleNewBooking.ts (L963) and emailReminderManager.ts (L197). When immediateDelete=true, the SendGrid batch is cancelled but the DB record is neither deleted nor marked cancelled=true, leaving orphaned records in the database. |
Supplementary Findings (13 findings)
Additional issues found by FriendlyReviewer that are not part of the golden (reference) set.
| Finding | File | Severity | Legitimate? |
|---|---|---|---|
Double processing of reminders during reschedule → duplicate side effects via both tRPC and handleNewBooking |
handleNewBooking.ts:961 | HIGH | ✓ Duplicate side effects |
remindersToCancel query does not filter null referenceId → SendGrid API call receives malformed data and fails |
scheduleEmailReminders.ts:41 | HIGH | ✓ API failure risk |
Cancellation loop wrapped in single try/catch → one failure blocks all remaining cancellations |
scheduleEmailReminders.ts:50 | HIGH | ✓ Cascade failure |
Custom onChange on PhoneInput overrides react-hook-form → phone value never propagates to form state |
WorkflowStepContainer.tsx:409 | HIGH | ✓ Form state broken |
Inconsistent lifecycle: EMAIL soft-cancelled (cancelled flag), SMS hard-deleted from DB |
handleCancelBooking.ts:488 | MEDIUM | ✓ Inconsistent behavior |
deleteScheduledSMSReminder always hard-deletes, missing immediateDelete param → inconsistent with EMAIL cancellation path |
smsReminderManager.ts:177 | MEDIUM | ✓ Missing param |
verificationCode never reset when action type or phone changes → stale code validates against wrong configuration |
WorkflowStepContainer.tsx:447 | MEDIUM | ✓ Stale state |
Unused import: Prisma imported but no longer used in this file |
workflows.tsx:1 | LOW | ✓ Dead import |
console.log for error logging instead of proper logger utility |
emailReminderManager.ts:228 | LOW | ✓ Inconsistent logging |
cancelled column nullable without DEFAULT false → consider adding default |
migration.sql:2 | LOW | ✓ Missing default |
Honest Analysis
FriendlyReviewer's full review
As posted on the PR — the complete output from FriendlyReviewer.
Hi there 👋
| 🌥️ Tech |
|---|
| 9 high, 3 medium, 3 low |
This merge request adds comprehensive workflow reminder management, introducing scheduled email and SMS reminders for booking workflows. The core direction is useful, but the implementation introduces several critical bugs and code quality issues that must be addressed before merging. The most significant issues include: (1) multiple async functions called without await inside forEach across 4 files, causing reminder cancellations to be silently dropped, (2) orphaned database records when immediateDelete=true cancels the SendGrid email but leaves the DB row untouched, (3) duplicate processing of workflow reminders during reschedule through both the tRPC router and handleNewBooking, (4) a fragile cancellation loop that aborts all remaining cancellations on the first error, (5) a SendGrid API call that will fail with malformed request data when referenceId is null, and (6) a React state bug where verificationCode persists across form changes. The review recommends blocking the merge until these issues are resolved.
These might need a close look
- 🔴 packages/features/bookings/lib/handleCancelBooking.ts (L483)
CRITICAL:forEach(async ...)callsdeleteScheduledEmailReminderanddeleteScheduledSMSReminderwithoutawait. Reminder cancellations become fire-and-forget and are silently dropped. Replace withfor...oforPromise.all. - 🔴 packages/trpc/server/routers/viewer/bookings.tsx (L488)
CRITICAL:forEach(async ...)fires reminder cancellation functions without awaiting them. Same fire-and-forget pattern ashandleCancelBooking.ts. - 🔴 packages/trpc/server/routers/viewer/workflows.tsx (L211, 375, 521, 576)
CRITICAL: Multiple instances offorEach(async ...)across the workflows router — reminders scheduled or cancelled withoutawait. Each instance silently swallows errors and drops operations. - 🔴 packages/features/bookings/lib/handleNewBooking.ts (L961)
BUG: Workflow reminders are processed during reschedule in both the tRPC router andhandleNewBooking, causing duplicate side effects. Consolidate reminder processing to a single code path. - 🔴 packages/features/bookings/lib/handleNewBooking.ts (L963)
CRITICAL: WhenimmediateDelete=true, the SendGrid email batch is cancelled but the DB record is not deleted or markedcancelled=true. This creates orphaned database records that will never be cleaned up. - 🔴 packages/features/ee/workflows/lib/emailReminderManager.ts (L197)
CRITICAL: SameimmediateDeleteorphaned record issue — SendGrid cancellation succeeds but the corresponding database row is left untouched, creating inconsistency between the external system and internal state. - 🔴 packages/features/ee/workflows/lib/scheduleEmailReminders.ts (L41)
BUG: TheremindersToCancelquery does not filter outnull referenceIdvalues. WhenreferenceIdis null, the subsequent SendGrid API call receives malformed data and fails. - 🔴 packages/features/ee/workflows/lib/scheduleEmailReminders.ts (L50)
CRITICAL: The entire cancellation loop is wrapped in a singletry/catchblock. If any single cancellation fails (e.g., SendGrid rate limit), the remaining cancellations are skipped entirely. Add per-item error handling. - 🔴 packages/features/ee/workflows/components/WorkflowStepContainer.tsx (L409)
BUG: CustomonChangehandler onPhoneInputoverrides react-hook-form'sregister, so the phone value never propagates to the form state. AlsoverificationCodeis never reset when action type or phone changes, causing stale code validation.
Worth checking
- 🟡 packages/features/bookings/lib/handleCancelBooking.ts (L488)
Inconsistent lifecycle: EMAIL reminders are soft-cancelled (cancelledflag set) while SMS reminders are hard-deleted from the database. Unify to a consistent approach. - 🟡 packages/features/ee/workflows/lib/smsReminderManager.ts (L177)
deleteScheduledSMSReminderalways hard-deletes records, missing theimmediateDeleteparameter that the email cancellation path supports. This creates inconsistent behavior between email and SMS cancellation. - 🟡 packages/features/ee/workflows/components/WorkflowStepContainer.tsx (L447)
verificationCodestate is never reset when the action type or phone number changes. A previously verified code remains valid for the wrong configuration, causing stale code validation issues.
Small things (take or leave)
- 🔵 packages/trpc/server/routers/viewer/workflows.tsx (L1)
Unused import:Prismais imported but no longer used after the workflow changes. Remove the unused import. - 🔵 packages/features/ee/workflows/lib/emailReminderManager.ts (L228)
console.logused for error logging instead of the project's structured logger. Replace for consistency and observability. - 🔵 packages/features/ee/workflows/lib/migrations/migration.sql (L2)
cancelledcolumn is nullable without a default value. Consider addingDEFAULT falseto ensure predictable behavior when records are created without explicitly setting this field.
FriendlyReviewer found both golden bugs (100% recall) on this PR, which is the strongest possible result against the reference set. The
forEach(async)fire-and-forget pattern was found across 4 files with 6 individual occurrences, exceeding the golden reference's coverage. TheimmediateDeleteorphaned record bug was precisely diagnosed.The supplementary findings are useful but mixed. The double processing of reminders during reschedule is a real defect that would cause duplicate side effects in production. The
verificationCodestate leak is a subtle React state bug that a human might miss. But the 'missing disabled prop for loading state' finding — while technically valid — is a frontend best practice rather than a bug.On the 600% extended coverage number: it's mathematically correct per the formula but the denominator is only 2 golden comments. A single PR with different findings against a small reference set produces absurd percentages. The real story is 13 supplementary findings, 7 of which are genuine HIGH-severity bugs.