Skip to content

Commit 9dcab47

Browse files
committed
Skip Sentry capture when token refresh fails with 401
When a token refresh fails with UnauthorizedError (401), this is an expected re-authentication flow — the refresh token is expired, revoked, or lacks required OAuth scopes. The app correctly logs out the user, but was also capturing this to Sentry, generating thousands of noise events (2.6k spike on April 19 after a scope change). Mirror the pattern already used in fetchCreatorStatus: console.warn for UnauthorizedError, Sentry.captureException only for unexpected errors. Fixes GUMROAD-MOBILE-2D
1 parent ba0a1af commit 9dcab47

2 files changed

Lines changed: 17 additions & 1 deletion

File tree

lib/request.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ export const useAPIRequest = <TResponse, TData = TResponse>(
100100
newAccessToken = await refreshToken();
101101
} catch (refreshError) {
102102
if (refreshError instanceof KeychainUnavailableError) throw error;
103-
Sentry.captureException(refreshError, { tags: { auth_path: "refresh_failed" } });
103+
if (refreshError instanceof UnauthorizedError) {
104+
console.warn(refreshError);
105+
} else {
106+
Sentry.captureException(refreshError, { tags: { auth_path: "refresh_failed" } });
107+
}
104108
await logout();
105109
throw error;
106110
}

tests/lib/use-api-request.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ describe("useAPIRequest", () => {
116116
expect(Sentry.captureException).not.toHaveBeenCalled();
117117
});
118118

119+
it("logs out without reporting to Sentry when refresh fails with UnauthorizedError", async () => {
120+
mockFetch.mockResolvedValueOnce(jsonResponse({}, 401));
121+
mockRefreshToken.mockRejectedValueOnce(new UnauthorizedError("Unauthorized"));
122+
123+
const { result } = renderUseAPIRequest();
124+
125+
await waitFor(() => expect(result.current.isError).toBe(true));
126+
expect(mockLogout).toHaveBeenCalledTimes(1);
127+
expect(Sentry.captureException).not.toHaveBeenCalled();
128+
expect(result.current.error).toBeInstanceOf(UnauthorizedError);
129+
});
130+
119131
it("logs out when refresh fails with a raw keychain error (write-side after server rotation)", async () => {
120132
mockFetch.mockResolvedValueOnce(jsonResponse({}, 401));
121133
const writeError = new Error("User interaction is not allowed");

0 commit comments

Comments
 (0)