Skip to content

fix(sdk): handle connect RPC errors consistently#1499

Open
rsbh wants to merge 4 commits intomainfrom
fix_sdk_connect_rpc_onerror
Open

fix(sdk): handle connect RPC errors consistently#1499
rsbh wants to merge 4 commits intomainfrom
fix_sdk_connect_rpc_onerror

Conversation

@rsbh
Copy link
Copy Markdown
Member

@rsbh rsbh commented Apr 1, 2026

Summary

  • Add handleConnectError utility in sdk/utils/error.ts that maps gRPC status codes (AlreadyExists, InvalidArgument, PermissionDenied, NotFound) to caller-defined handlers with a Default fallback
  • Fix unhandled promise rejections across 12 files where mutateAsync + onError was used without try/catch
  • Replace inconsistent error handling (generic error.message, console.error only, duplicate toasts) with structured handleConnectError calls
  • Export handleConnectError from @raystack/frontier/hooks for external consumers

Test plan

  • Verify create team with duplicate name shows inline form error
  • Verify update project with invalid input shows toast with server message
  • Verify block/unblock user shows contextual error message on failure
  • Verify invite user shows "Invitation already exists" on duplicate
  • Verify cancel subscription shows "Failed to cancel subscription" on unknown error
  • Verify no unhandled promise rejections in browser console

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Apr 6, 2026 6:15am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Improvements
    • More specific, contextual error messages and descriptions across invites, members, projects, teams, billing, domains, and preferences (e.g., "Already exists", "Permission denied", "Invalid input", "Not found").
    • Field-level validation surfaced for team and project creation and clearer success/failure toasts across dialogs and workflows.
  • New
    • Centralized error handling for consistent, user-facing notifications throughout the app.

Walkthrough

Adds a centralized handleConnectError utility and refactors many UI mutation error handlers: removes mutation-level onError callbacks, wraps RPC calls in submit-time try/catch blocks, and delegates error-to-toast and form-error mapping to handleConnectError across admin and React views.

Changes

Cohort / File(s) Summary
Error Handling Infrastructure
web/sdk/utils/error.ts, web/sdk/hooks/index.ts
Introduce handleConnectError and re-export it from hooks.
Admin Invite & Project Members
web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx, web/sdk/admin/views/organizations/details/projects/use-add-project-members.tsx, web/sdk/admin/views/users/list/invite-users.tsx
Move mutation error handling into submit-time try/catch and delegate to handleConnectError for Connect-code-specific toasts.
Admin User Block/Unblock
web/sdk/admin/views/users/details/security/block-user.tsx
Remove per-mutation onError; add handleAction wrapper that calls mutations and routes errors to handleConnectError.
React Hooks & Preferences
web/sdk/react/hooks/usePreferences.ts
Remove mutation onError; wrap updatePreferences in try/catch and forward errors to handleConnectError; minor signature/formatting tweaks.
Members & Teams Invite Flows
web/sdk/react/views/members/invite-member-dialog.tsx, web/sdk/react/views/teams/details/invite-team-member-dialog.tsx, web/sdk/react/views/teams/list/add-team-dialog.tsx, web/sdk/react/views-new/members/...
Shift error handling from mutation options to submit-time try/catch; use handleConnectError to show targeted toasts or set form errors (e.g., AlreadyExists → field error).
Teams & Projects Update/Delete Flows
web/sdk/react/views/teams/details/team-general.tsx, web/sdk/react/views/projects/details/project-general.tsx, web/sdk/react/views/projects/details/delete-project-dialog.tsx, web/sdk/react/views-new/projects/...
Replace mutation onError handlers with try/catch around submit/update/delete and delegate to handleConnectError for code-specific messages and descriptions.
Plans / Subscriptions
web/sdk/react/views/plans/hooks/usePlans.tsx
Remove mutation onError handlers; wrap checkout/change/cancel flows in try/catch and use handleConnectError; remove unused billingAccount from destructure/deps.
Security / Domains & Profile
web/sdk/react/views-new/security/..., web/sdk/react/views-new/profile/profile-view.tsx
Wrap domain/profile RPC calls in try/catch and centralize error-to-toast mapping via handleConnectError; remove mutation-level onError callbacks for these flows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • paanSinghCoder
  • rohanchkrabrty

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 1, 2026

Pull Request Test Coverage Report for Build 24021180111

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage remained the same at 41.238%

Totals Coverage Status
Change from base Build 24020284764: 0.0%
Covered Lines: 14931
Relevant Lines: 36207

💛 - Coveralls

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/sdk/admin/views/organizations/details/projects/use-add-project-members.tsx (1)

58-79: ⚠️ Potential issue | 🟠 Major

Don’t conflate mutation failure with refetch failure in one catch.

A refetch error after successful member addition currently falls into the same handler map, which can show misleading failure messaging for an already-successful write.

Proposed fix
-      try {
+      try {
         const principal = `app/user:${userId}`;
         const resource = `app/project:${projectId}`;
         await createPolicy(
           create(CreatePolicyRequestSchema, {
             body: {
               roleId: DEFAULT_ROLES.PROJECT_VIEWER,
               principal,
               resource,
             },
           }),
         );
         toast.success(`${memberLabel} added`);
-        await refetch();
-        return projectMembers;
       } catch (error: unknown) {
         handleConnectError(error, {
           AlreadyExists: () => toast.error(`${memberLabel} already exists in this project`),
           PermissionDenied: () => toast.error("You don't have permission to perform this action"),
           InvalidArgument: (err) => toast.error('Invalid input', { description: err.message }),
           Default: (err) => toast.error('Something went wrong', { description: err.message }),
         });
+        return;
+      }
+
+      try {
+        await refetch();
+      } catch {
+        toast.error(`${memberLabel} added, but failed to refresh the list`);
       }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea972d5d-610a-4d8a-b513-a874a6d47283

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef1890 and d38b7be.

📒 Files selected for processing (14)
  • web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx
  • web/sdk/admin/views/organizations/details/projects/use-add-project-members.tsx
  • web/sdk/admin/views/users/details/security/block-user.tsx
  • web/sdk/admin/views/users/list/invite-users.tsx
  • web/sdk/hooks/index.ts
  • web/sdk/react/hooks/usePreferences.ts
  • web/sdk/react/views/members/invite-member-dialog.tsx
  • web/sdk/react/views/plans/hooks/usePlans.tsx
  • web/sdk/react/views/projects/details/delete-project-dialog.tsx
  • web/sdk/react/views/projects/details/project-general.tsx
  • web/sdk/react/views/teams/details/invite-team-member-dialog.tsx
  • web/sdk/react/views/teams/details/team-general.tsx
  • web/sdk/react/views/teams/list/add-team-dialog.tsx
  • web/sdk/utils/error.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/sdk/react/views/plans/hooks/usePlans.tsx (1)

82-129: ⚠️ Potential issue | 🟠 Major

Move post-success callbacks outside try/catch blocks to prevent masking client exceptions as RPC errors.

The onSuccess() calls at lines 118, 162, and 282 execute within the same try block as their respective mutations. If onSuccess throws after a successful RPC response, the catch handler will misreport it as a backend error, display a false error toast, and hide the real client bug. Extract the success-side logic outside the try/catch for all three functions: checkoutPlan, changePlan, and cancelSubscription.

Proposed pattern
-        try {
-          const resp = await changeSubscriptionMutation(
-            create(ChangeSubscriptionRequestSchema, {
-              id: activeSubscription?.id,
-              change: {
-                case: 'planChange',
-                value: {
-                  plan: planId,
-                  immediate: immediate
-                }
-              }
-            })
-          );
-          if (resp?.phase) {
-            onSuccess();
-          }
-        } catch (error) {
+        let resp;
+        try {
+          resp = await changeSubscriptionMutation(
+            create(ChangeSubscriptionRequestSchema, {
+              id: activeSubscription?.id,
+              change: {
+                case: 'planChange',
+                value: {
+                  plan: planId,
+                  immediate: immediate
+                }
+              }
+            })
+          );
+        } catch (error) {
           handleConnectError(error, {
             PermissionDenied: () =>
               toast.error("You don't have permission to perform this action"),
             InvalidArgument: err =>
               toast.error('Failed to change plan', {
                 description: err.message
               }),
             NotFound: err =>
               toast.error('Not found', { description: err.message }),
             Default: err =>
               toast.error('Failed to change plan', { description: err.message })
           });
+          return;
+        }
+
+        if (resp?.phase) {
+          onSuccess();
         }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85b54ad6-8da6-4ce6-aa45-4245ec8cbf74

📥 Commits

Reviewing files that changed from the base of the PR and between d38b7be and f45db61.

📒 Files selected for processing (2)
  • web/sdk/react/views/plans/hooks/usePlans.tsx
  • web/sdk/utils/error.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/sdk/utils/error.ts

rsbh and others added 3 commits April 6, 2026 11:24
Replace inconsistent onError callbacks and unhandled promise rejections
with try/catch blocks using the new handleConnectError utility across
12 files. Maps gRPC status codes (AlreadyExists, InvalidArgument,
PermissionDenied, NotFound) to appropriate user-facing error messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Default handler for checkoutPlan and changePlan in usePlans
- Handle string-thrown errors in handleConnectError utility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
web/sdk/utils/error.ts (1)

30-32: Preserve structured context in fallback error logging.

At Line 31, logging only err.message drops useful debugging context (for example code and metadata). Prefer logging the full error object or a structured payload in the default handler.

♻️ Suggested tweak
-  const defaultHandler =
-    handlers?.Default ?? ((err: ConnectError) => console.error(err.message));
+  const defaultHandler =
+    handlers?.Default ??
+    ((err: ConnectError) =>
+      console.error('frontier:sdk:: connect rpc error', {
+        code: err.code,
+        message: err.message,
+        error: err
+      }));
web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx (1)

11-12: Backfill role when the async default resolves.

useForm only applies defaultValues on the first render. If this dialog mounts before OrganizationContext.roles is populated, defaultRoleId never reaches the form and the submit path falls back to roleIds: [] until the user manually re-selects a role.

♻️ Proposed fix
-import { useContext, useMemo } from 'react';
+import { useContext, useEffect, useMemo } from 'react';
…
   const methods = useForm<InviteSchemaType>({
     resolver: zodResolver(inviteSchema),
     defaultValues: {
       role: defaultRoleId
     }
   });
+
+  useEffect(() => {
+    if (defaultRoleId && !methods.getValues('role')) {
+      methods.setValue('role', defaultRoleId, { shouldValidate: true });
+    }
+  }, [defaultRoleId, methods]);

Also applies to: 53-63, 90-94


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7107438c-15fa-42e9-94e9-7cf44c4f6155

📥 Commits

Reviewing files that changed from the base of the PR and between f45db61 and f7396f0.

📒 Files selected for processing (14)
  • web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx
  • web/sdk/admin/views/organizations/details/projects/use-add-project-members.tsx
  • web/sdk/admin/views/users/details/security/block-user.tsx
  • web/sdk/admin/views/users/list/invite-users.tsx
  • web/sdk/hooks/index.ts
  • web/sdk/react/hooks/usePreferences.ts
  • web/sdk/react/views/members/invite-member-dialog.tsx
  • web/sdk/react/views/plans/hooks/usePlans.tsx
  • web/sdk/react/views/projects/details/delete-project-dialog.tsx
  • web/sdk/react/views/projects/details/project-general.tsx
  • web/sdk/react/views/teams/details/invite-team-member-dialog.tsx
  • web/sdk/react/views/teams/details/team-general.tsx
  • web/sdk/react/views/teams/list/add-team-dialog.tsx
  • web/sdk/utils/error.ts
✅ Files skipped from review due to trivial changes (1)
  • web/sdk/hooks/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • web/sdk/admin/views/organizations/details/projects/use-add-project-members.tsx
  • web/sdk/react/views/members/invite-member-dialog.tsx
  • web/sdk/react/views/teams/list/add-team-dialog.tsx
  • web/sdk/react/views/plans/hooks/usePlans.tsx

Replace inconsistent error handling in views-new with handleConnectError
utility across 14 files. Remove onError callbacks, add try/catch blocks,
and map gRPC status codes to user-facing toasts via toastManager.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/sdk/react/views-new/members/components/update-role-dialog.tsx (1)

97-106: ⚠️ Potential issue | 🟠 Major

Stop role update flow when policy deletions fail

At Line 104, failed deletions are only logged and execution continues to create a new policy, then shows success. This can leave stale policies while reporting a successful update. Treat deletion failures as a hard error so the catch handler can show an error toast.

💡 Suggested fix
       const deleteErrors = deleteResults
         .filter(
           (result): result is PromiseRejectedResult =>
             result.status === 'rejected'
         )
         .map(result => result.reason);

       if (deleteErrors.length > 0) {
-        console.warn('Some policy deletions failed:', deleteErrors);
+        throw (
+          deleteErrors[0] instanceof Error
+            ? deleteErrors[0]
+            : new Error('Failed to update member role')
+        );
       }

Also applies to: 120-125


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db87bc19-378e-49f1-813b-307bc5222ff3

📥 Commits

Reviewing files that changed from the base of the PR and between f7396f0 and 68e49f3.

📒 Files selected for processing (14)
  • web/sdk/react/views-new/general/components/delete-organization-dialog.tsx
  • web/sdk/react/views-new/general/general-view.tsx
  • web/sdk/react/views-new/members/components/invite-member-dialog.tsx
  • web/sdk/react/views-new/members/components/remove-member-dialog.tsx
  • web/sdk/react/views-new/members/components/update-role-dialog.tsx
  • web/sdk/react/views-new/profile/profile-view.tsx
  • web/sdk/react/views-new/projects/components/add-project-dialog.tsx
  • web/sdk/react/views-new/projects/components/delete-project-dialog.tsx
  • web/sdk/react/views-new/projects/components/edit-project-dialog.tsx
  • web/sdk/react/views-new/projects/components/remove-member-dialog.tsx
  • web/sdk/react/views-new/projects/project-details-view.tsx
  • web/sdk/react/views-new/security/components/add-domain-dialog.tsx
  • web/sdk/react/views-new/security/components/delete-domain-dialog.tsx
  • web/sdk/react/views-new/security/components/verify-domain-dialog.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants