Skip to content

fix: catch mutateAsync rejection in GcpCallback to prevent unhandled promise errors #SUPERLOG - #452

Open
superlog-app[bot] wants to merge 1 commit into
mainfrom
superlog/gcp-callback-mutate-try-catch
Open

fix: catch mutateAsync rejection in GcpCallback to prevent unhandled promise errors #SUPERLOG#452
superlog-app[bot] wants to merge 1 commit into
mainfrom
superlog/gcp-callback-mutate-try-catch

Conversation

@superlog-app

@superlog-app superlog-app Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

When a user selects a Google Cloud project on /connect/gcp and clicks Connect project, the page calls connect.mutateAsync(selectedProjectId) inside an async onClick handler without a surrounding try/catch. If GCP provisioning fails (e.g. because Superlog's superlog-infra org policy blocks adding the Cloud Logging service agent to the Pub/Sub topic IAM policy), mutateAsync re-throws the ApiError and — because nothing catches it in the handler — it becomes an unhandled promise rejection. The OTel window.unhandledrejection listener records it as a browser.exception ERROR span, opening a production incident.

The user-facing error is already shown via connect.error (the red banner at line 126), so no UI change is needed; only the unhandled rejection needs to be suppressed.

Root cause

React Query's mutateAsync re-throws on failure (unlike mutate). The established codebase convention — already applied in GcpConnectFlow.tsx — is to wrap mutateAsync in a try/catch and let the mutation's .error state drive the UI:

// GcpConnectFlow.tsx (existing pattern)
try {
  const { url } = await start.mutateAsync();
  // ...
} catch {
  // The mutation error surfaces via start.error below.
}

GcpCallback.tsx was missing this guard.

Fix

Wrapped the mutateAsync call in GcpCallback.tsx in an identical try/catch. The window.location.assign only runs on success; connect.error continues to display the error message when the call fails.

Related infrastructure issue

The underlying GCP provisioning failure (502 from setIamPolicy) is caused by a constraints/iam.allowedPolicyMemberDomains org policy on superlog-infra that rejects Cloud Logging service agents from customer projects. That requires a separate GCP infrastructure fix (allow gcp-sa-logging.iam.gserviceaccount.com in the policy). This PR only addresses the browser-side unhandled rejection.


Incident: ee226810-502e-4c6c-ae61-b6888b87c7c4


Was this PR helpful? Leave feedback — goes straight to the Superlog team.


Summary by cubic

Prevent unhandled promise rejections in the GCP connect flow by wrapping connect.mutateAsync in a try/catch in GcpCallback. On failure, the existing error banner shows the message; on success we redirect to /connect/gcp?gcp=connected.

Written for commit bf207e6. Summary will update on new commits.

Review in cubic

…promise errors #SUPERLOG

Delivery-Id: 1695164479158365e8c9ca7ec82f30f8db670ec8bfe18a519db5fa0649307350
Delivery-Base: main

@superlog-app superlog-app Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Observability review

  • 1 blocking

try {
await connect.mutateAsync(selectedProjectId);
window.location.assign("/connect/gcp?gcp=connected");
} catch {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

traces · blocking — Record the error on the active span inside the catch block

Call span.recordException(e) and span.setStatus({ code: SpanStatusCode.ERROR }) (or the repo's equivalent helper) inside the catch so the provisioning failure is visible in traces; without this, swallowing the rejection removes the only browser-side signal that was previously captured by the unhandledrejection listener, leaving operators unable to correlate user-facing GCP connect failures with backend 502s.

Suggested change
} catch {
} catch (e) {
// connect.error surfaces via the error banner above.
const span = trace.getActiveSpan();
if (span) {
span.recordException(e as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
}
}

Useful? React with 👍 / 👎.

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.

1 participant