Conversation
The initializeOrganization transaction runs 20+ DB operations (controls, policies, tasks, versions, requirement maps) and was hitting Prisma's default 5s timeout for users selecting multiple frameworks. - Set global transaction timeout to 30s across all 5 Prisma client instances - Clean up partially created org on failure to prevent orphans on retry - Surface actual error messages instead of generic "Failed to create organization" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address Bugbot review: if setActiveOrganization succeeded but a later step (revalidatePath) threw, the cleanup would delete a fully initialized org while the session still referenced it. Now cleanup is disabled after activation, and revalidatePath errors are caught separately since they are non-critical. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
isOnboarding is true during the server action but wasn't used in the disabled prop — only isSubmitting (react-hook-form) was, which resets after the synchronous onSubmit handler. This allowed double-clicks to create duplicate orgs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to previous org When users create an additional org via create-additional flow, they get trapped in the onboarding funnel with no way to go back. This adds a Cancel button (visible only when user has other completed orgs) to: - Pre-payment setup form: navigates back to root (no org to delete yet) - Upgrade page: deletes incomplete org, switches to previous org - Post-payment onboarding: deletes incomplete org, switches to previous org Includes confirmation step before deletion to prevent accidental cancels. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…before delete - Reject cancel on orgs with onboardingCompleted=true - Switch activeOrganization BEFORE deleting so session never references a deleted org (prevents dangling session on slow client redirect) - Fail cancel if org switch fails rather than leaving orphaned state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Don't expose raw Prisma/DB error messages (like constraint violations or connection details) in user-facing toasts. Log the raw error to console for debugging, show a generic user-friendly message in the toast. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refuse to delete org if no fallback org exists server-side. Prevents race condition where other orgs are removed between page render (which checks hasOtherOrgs) and action execution, which would leave the session pointing at a deleted org. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If setActiveOrganization succeeds but organization.delete fails, roll back the active org to the original one so the session stays consistent with what the user sees on screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add List-Unsubscribe headers and throttle email sends - Add List-Unsubscribe and List-Unsubscribe-Post headers to all outbound emails for Gmail/RFC 8058 one-click unsubscribe compliance - Reduce email queue concurrency from 30 to 10 - Add 1s delay between sends to avoid email spikes that trigger reputation systems Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use setTimeout instead of wait.for for email throttling wait.for suspends execution and frees the concurrency slot, defeating the throttling purpose. setTimeout holds the slot occupied for 1s, actually spacing out sends. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use real recipient email for unsubscribe URL, not test override When RESEND_TO_TEST is set, toAddress becomes the test email. The unsubscribe URL should always reference the real recipient (params.to) so the token validates correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove List-Unsubscribe-Post, add mailto fallback The one-click POST handler doesn't exist yet (unsubscribe page is GET only). Removed List-Unsubscribe-Post to avoid claiming RFC 8058 support we don't have. Added mailto fallback for broader client compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add RFC 8058 one-click unsubscribe POST endpoint - New POST /v1/email/unsubscribe endpoint that accepts email+token via query params, verifies HMAC token, and unsubscribes the user - No auth required (token IS the auth, Gmail needs to POST directly) - Re-add List-Unsubscribe-Post header now that the handler exists - List-Unsubscribe URL points to API endpoint for one-click POST Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove dead import, use timing-safe token comparison - Remove unused getUnsubscribeUrl import from send-email.ts - Use crypto.timingSafeEqual for HMAC token verification in unsubscribe endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: guard against type confusion on query/body params CodeQL flagged that query params could be arrays. Explicitly coerce to string before using. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: include findingNotifications in unsubscribe preferences Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR SummaryMedium Risk Overview Improves onboarding resilience and escape hatches: minimal org creation now stores both framework display names and raw Operational tweaks: sets Prisma Reviewed by Cursor Bugbot for commit 082501f. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
Deep path import @trycompai/email/lib/unsubscribe resolves to source .ts files which Trigger's esbuild can't find in dist/. Use barrel import from @trycompai/email instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| const expectedToken = generateUnsubscribeToken(email); | ||
| const tokensMatch = | ||
| expectedToken.length === token.length && | ||
| timingSafeEqual(Buffer.from(expectedToken), Buffer.from(token)); |
There was a problem hiding this comment.
String length guard doesn't prevent timingSafeEqual buffer mismatch
Low Severity
The guard expectedToken.length === token.length compares JavaScript string lengths, but timingSafeEqual compares buffer byte lengths. If an attacker submits a token containing multi-byte UTF-8 characters whose JS string length matches the expected token's length (43 chars for SHA-256 base64url), the string-length check passes, but Buffer.from(token) produces more bytes than Buffer.from(expectedToken), causing timingSafeEqual to throw an unhandled RangeError. This results in a 500 instead of a 400.
Reviewed by Cursor Bugbot for commit 80db5d9. Configure here.
…-flight Prevents race between org deletion and completeOnboarding by hiding the cancel button when isOnboarding or isFinalizing is true. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: use barrel import for email package (Trigger build fix)
…imeout fix(onboarding): fix org creation timeout and improve error handling
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0ec7eed. Configure here.
| try { | ||
| await db.organization.delete({ | ||
| where: { id: parsedInput.organizationId }, | ||
| }); |
There was a problem hiding this comment.
Cancel action can delete completed org via race
Medium Severity
The onboardingCompleted check at line 45 and the db.organization.delete at line 86 are not atomic. If a concurrent request (e.g., a background job or another browser tab) sets onboardingCompleted: true between the check and the delete, a fully completed organization with all its production data gets cascade-deleted. The delete WHERE clause only uses id and does not re-verify onboardingCompleted: false.
Reviewed by Cursor Bugbot for commit 0ec7eed. Configure here.
| <CancelOnboardingButton | ||
| organizationId={organization.id} | ||
| hasOtherOrgs={hasOtherOrgs && !isOnboarding && !isFinalizing} | ||
| /> |
There was a problem hiding this comment.
Dynamic prop can unmount cancel button mid-action
Low Severity
Passing hasOtherOrgs={hasOtherOrgs && !isOnboarding && !isFinalizing} causes CancelOnboardingButton to unmount when isOnboarding or isFinalizing flip to true. If the cancel server action is already in-flight, the component unmounts, the onSuccess callback with window.location.assign may not fire, and the user gets stranded on a page for a now-deleted organization.
Reviewed by Cursor Bugbot for commit 0ec7eed. Configure here.
#2512) * fix(onboarding): add initialize-organization trigger task and recovery guard If createOrganizationMinimal partially fails (org created but initializeOrganization doesn't run), the org ends up with no framework instances, controls, policies, or tasks. completeOnboarding now detects this and runs initializeOrganization as recovery before triggering the onboard job. A standalone Trigger.dev task allows manual re-runs from the dashboard for orgs already in this broken state. Also saves raw framework IDs to context for reliable recovery lookups and upserts the onboarding record in case that was also missing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(onboarding): extract resolveFrameworkIds to shared helper Deduplicates the resolveFrameworkIds logic that was copied in both complete-onboarding.ts and initialize-organization.ts. Now lives in actions/organization/lib/resolve-framework-ids.ts alongside initialize-organization.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
🎉 This PR is included in version 3.21.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |


This is an automated pull request to release the candidate branch into production, which will trigger a deployment.
It was created by the [Production PR] action.