Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions static/app/bootstrap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ async function promiseRequest(url: string) {
};
return [json, response.statusText, responseMeta];
}
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw [response.status, response.statusText];
} catch (error: any) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw [error.status, error.statusText];
return null;
} catch {
return null;
}
}

Expand Down
34 changes: 0 additions & 34 deletions static/app/bootstrap/initializeSdk.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,40 +44,6 @@ describe('initializeSdk', () => {
})
);
});

it('filters malformed [null,null] unhandled rejections', () => {
initializeSdk({
...window.__initialData,
apmSampling: 1,
sentryConfig: {
allowUrls: [],
dsn: '',
release: '',
tracePropagationTargets: [],
},
});

const initConfig = jest.mocked(Sentry.init).mock.calls.slice(-1)[0]?.[0];
expect(initConfig?.beforeSend).toBeDefined();

const event = {
// The SDK has not normalized this to the stored `[null,null]` string yet.
message: [null, null] as unknown as string,
exception: {
values: [
{
type: 'Error',
value: ',',
mechanism: {
type: 'auto.browser.global_handlers.onunhandledrejection',
},
},
],
},
} as Sentry.ErrorEvent;

expect(initConfig?.beforeSend?.(event, {originalException: [null, null]})).toBeNull();
});
});

describe('isFilteredRequestErrorEvent', () => {
Expand Down
21 changes: 1 addition & 20 deletions static/app/bootstrap/initializeSdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,7 @@ export function initializeSdk(config: Config) {
},

beforeSend(event, hint) {
if (
isFilteredRequestErrorEvent(event) ||
isEventWithFileUrl(event) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Filter removed too early

Medium Severity

Removing isNullTupleUnhandledRejectionEvent drops coverage for the Django preload-data.html path, which still rejects failed preloads as an undefined status tuple. Those early unhandled rejections can resurface as the same noisy frontend errors this change is trying to eliminate.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c95cdd0. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

its okay if it makes some noise while deploying

isNullTupleUnhandledRejectionEvent(hint)
) {
if (isFilteredRequestErrorEvent(event) || isEventWithFileUrl(event)) {
return null;
}

Comment on lines 185 to 191

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: Removing the isNullTupleUnhandledRejectionEvent filter without adding .catch() handlers to promises in preload-data.html will cause previously ignored unhandled promise rejections to be reported to Sentry.
Severity: MEDIUM

Suggested Fix

Update the preload-data.html template to attach a no-op .catch(() => {}) handler to the promises assigned to window.__sentry_preload. This will prevent them from becoming unhandled rejections if they fail before being consumed by the application's bootstrap logic, while still allowing the rejection to be properly handled by the consumer later.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: static/app/bootstrap/initializeSdk.tsx#L185-L191

Potential issue: The `preload-data.html` template creates promises for preloading data
and stores them on the `window` object. These promises, created via `promiseRequest()`,
can reject, especially on network errors. They lack a `.catch()` handler. While they are
eventually consumed and their rejections are caught within `bootstrapRequests.tsx`, a
race condition exists. If a promise rejects before it is consumed, it triggers an
unhandled promise rejection. The pull request removes the
`isNullTupleUnhandledRejectionEvent` filter, which previously discarded these specific
rejections. Consequently, these transient unhandled rejections will now be captured and
sent as events to Sentry.

Also affects:

  • static/app/bootstrap/index.tsx:67~75

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

its okay if it makes some noise while deploying

Expand Down Expand Up @@ -279,21 +275,6 @@ export function isEventWithFileUrl(event: Event): boolean {
return !!event.request?.url?.startsWith('file://');
}

/**
* Ignore unhandled rejections of `[null, null]`. The SDK keeps the original
* array in the hint until after `beforeSend`, then normalizes it to the
* `[null,null]` message shown in the stored event.
*/
function isNullTupleUnhandledRejectionEvent(hint: Sentry.EventHint): boolean {
const originalException = hint.originalException;

return (
Array.isArray(originalException) &&
originalException.length === 2 &&
originalException.every(value => value === null)
);
}

/** Tag and set fingerprint for UndefinedResponseBodyError events */
function handlePossibleUndefinedResponseBodyErrors(event: Event): void {
// One or both of these may be undefined, depending on the type of event
Expand Down
6 changes: 3 additions & 3 deletions static/app/types/system.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ declare global {
*/
__sentry_preload?: {
orgSlug?: string;
organization?: Promise<ApiResult>;
organization?: Promise<ApiResult | null>;
organization_fallback?: Promise<ApiResult>;
projects?: Promise<ApiResult>;
projects?: Promise<ApiResult | null>;
projects_fallback?: Promise<ApiResult>;
teams?: Promise<ApiResult>;
teams?: Promise<ApiResult | null>;
teams_fallback?: Promise<ApiResult>;
};
/**
Expand Down
Loading