Skip to content

Skipped Reporting of 302 and Request abort error #4881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 22, 2025

Conversation

SukhvirKooner
Copy link
Contributor

@SukhvirKooner SukhvirKooner commented Jan 28, 2025

Fixes: #4873

Summary

Redirect requests (301/302)

Before
Screenshot 2025-01-27 at 10 08 48 PM
After
Screenshot 2025-01-27 at 10 09 16 PM

I have successfully resolved the 302 redirect request issue by updating the Axios error handling logic. Specifically, I added 302 to the list of status codes that should bypass error logging:
(error.response && [302, 403, 404, 405, 412].includes(error.response.status))
This ensures that when a 302 status is returned, Axios will handle it gracefully without triggering unnecessary error logs or forwarding it to Sentry.

Request aborted Error

Error ScreenShort
Screenshot 2025-01-28 at 12 39 33 PM
I have made the key changes to the original code to handle the Error particular during navigation:

  1. Page State Tracking:
let isNavigating = false;
let isReloading = false;
let pendingRequests = new Set();
  • Added variables to track navigation state and keep track of pending requests
  • This helps manage network requests during page transitions
  1. Abort Controller Integration:
    const abortController = new AbortController();
  • Added a central AbortController to cancel pending requests
  1. Navigation Event Handling:
window.addEventListener('navigate', () => {
  isNavigating = true;
  abortController.abort();
  setTimeout(() => {
    isNavigating = false;
  }, 100);
});

window.addEventListener('beforeunload', () => {
  abortController.abort();
});
  • Listens for navigation events and aborts pending requests
  • Uses a timeout to reset the navigation state
  • Handles page unload events
  1. Request Tracking in Interceptors:
client.interceptors.request.use(config => {
  config.signal = abortController.signal;
  pendingRequests.add(config);
  return config;
});
  • Adds abort signal to each request
  • Tracks pending requests in the Set
  1. Enhanced Error Handling:
if (
  isNavigating || 
  isReloading ||
  axios.isCancel(error) ||
  error.code === 'ERR_CANCELED' ||
  error.code === 'ECONNABORTED' ||
  (error.response && [302, 403, 404, 405, 412].includes(error.response.status))
) {
  return Promise.reject(error);
}
  • Added comprehensive error checking
  • Silently handles navigation-related cancellations and specific HTTP status codes
  • Added 302 status code to the suppressed errors list
  1. Cleanup Function:
export function cleanupRequests() {
  abortController.abort();
  pendingRequests.clear();
}
  • Added function to clean up pending requests
  • Useful when components unmount

Despite these suppression mechanisms, legitimate errors unrelated to Request aborted or Redirect requests (301/302) will still be logged and captured in Sentry for further monitoring and troubleshooting.

Review guidance

1. Rebuild with PR changes

  • Pull this branch, restart the dev server.

2. Add debug logs

  • Cancellation branch at the top of the ignore‑conditional:
    if (isCancel(error) || ) {
      console.log('[DEBUG] request canceled or ignored:', error.message);
      return Promise.reject(error);
    }
    
  • Sentry branch at the top of the production-reporting block:
} else {
  console.log('[SENTRY] WOULD REPORT:', { message, url, status });
  Sentry.withScope();
}
  • Expose the controller for manual aborts:
// after `let abortController = new AbortController()`
window._axiosAbortController = abortController;

Test request abort suppression

  • In the app, trigger an Axios request (e.g., navigate to a published channel page).
  • Open DevTools → Console and run:
window._axiosAbortController.abort();
  • Expect to see only:

[DEBUG] request canceled or ignored: canceled

No [SENTRY] WOULD REPORT: line should appear.

This confirms that manually aborted requests are successfully caught and suppressed before they can be reported to Sentry. By logging only [DEBUG] request canceled or ignored and not triggering the [SENTRY] WOULD REPORT: log

export function cleanupRequests() {
abortController.abort();
pendingRequests.clear();
}
Copy link
Member

Choose a reason for hiding this comment

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

Is this being used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The exported cleanupRequests function is meant to be called when a component unmounts. It aborts any pending requests and clears the pendingRequests set to prevent memory leaks or unwanted side effects.

Copy link
Member

Choose a reason for hiding this comment

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

Right! Unfortunately I don't see any usages of this function. Makes me wonder whether we really need it, particularly within the scope of this task?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it! I'll go ahead and remove it.

// Reset the flag after navigation is likely complete
setTimeout(() => {
isNavigating = false;
}, 100);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the use of a delay here is a reliable way to set the navigation to false. I think it would be better to do this after an abort has been confirmed. You could consider adding an event listener to an controller.signal instance and do the setting there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right—relying on a fixed delay is not a reliable way to reset the navigation flag. Using an event listener on the AbortController.signal makes a lot more sense since it ensures the state update happens precisely when the abort is confirmed. I'll update as you suggested.

if (
isNavigating ||
isReloading ||
axios.isCancel(error) ||
Copy link
Member

Choose a reason for hiding this comment

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

Let's import the isCancel instead so we can clear the linting error below ie;
import {isCancel} from 'axios' and this line changes to isCancel(error) ||

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the suggestion! I'll make sure to update it accordingly.

@SukhvirKooner
Copy link
Contributor Author

Hi @akolson ,
I have removed the unused functions and updated the code as suggested.

Copy link
Member

@akolson akolson left a comment

Choose a reason for hiding this comment

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

Changes seem to make sense. Thanks @SukhvirKooner. I'll invite @nucleogenesis @AlexVelezLl @ozer550 incase of any further feedback before we proceed with the approval

@akolson
Copy link
Member

akolson commented Feb 20, 2025

@SukhvirKooner It looks like the linting check is failing, could you please look into this?

Also, @nucleogenesis @AlexVelezLl @ozer550, any additional thoughts on this?

@SukhvirKooner
Copy link
Contributor Author

Hi @akolson,
Thank you for pointing this out. The linting issue has been resolved. Please let me know if there’s anything else you’d like me to review.

Comment on lines 52 to 53
const newAbortController = new AbortController();
abortController.signal = newAbortController.signal;

// Listen for the abort event to reset the navigation flag
newAbortController.signal.addEventListener('abort', () => {
isNavigating = false;
});

// Abort pending requests when navigation begins
abortController.abort();
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need a new abort controller here? I'm not super familiar w/ AbortController, but how I read this is that it creates newAbortController, then sets the abortController.signal to the default value of a newly created AbortController instance. Then you put the event listener on that signal using the newAbortController (which now also sets the listener on the original abortController.signal unless I'm mistaken) - and then you call the abort() on the original controller.

So I'm wondering if this was done because you need a fresh AbortSignal value assigned to the original abortController value created above for some reason?

If it works it works, but perhaps a comment explaining why we need this temporary abort controller object for it's signal value alone would be helpful for anybody looking at this in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, The reason for creating a new AbortController and assigning its signal to the original one is to ensure that each navigation event gets a fresh AbortSignal. This allows proper cancellation of only the relevant pending requests tied to that specific navigation.

Without a fresh signal, previously aborted requests might interfere with new requests triggered by subsequent navigations. The fresh AbortSignal ensures each navigation cycle has its own independent abort handling.

I'll add a comment in the code to clarify this for future reference. Thanks again for pointing it out!

@@ -82,6 +129,8 @@ client.interceptors.response.use(
Network: {
lastOffline: lastOffline ? `${Date.now() - lastOffline}ms ago` : 'never',
online: navigator.onLine,
isNavigating,
pendingRequestCount: pendingRequests.size,
Copy link
Member

Choose a reason for hiding this comment

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

is there any significance in reporting this to sentry?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reporting isNavigating and pendingRequestCount to Sentry could be useful for debugging network-related issues:

  • isNavigating: Helps correlate errors with page transitions, potentially revealing issues triggered during navigation.
  • pendingRequestCount: Shows the number of pending requests when an error occurs, assisting in identifying performance bottlenecks or race conditions.

If there are no related issues currently, we can skip this. However, it might be valuable for future debugging.

Copy link
Member

Choose a reason for hiding this comment

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

I am not entirely sure there is a lot to deduce from these two pieces of information when sent to sentry

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a fair point. If these values alone don’t provide enough insight, we could consider pairing them with additional context like error codes to make them more meaningful in Sentry. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I think error codes would not make much of a difference. My point is this; we don't need to bloat the sentry reports especially if we are not getting much benefit from the data we are trying to report.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand your point, and I’ll remove this right away to keep the Sentry reports concise and relevant. Let me know if there’s anything else you’d like me to adjust.

@akolson
Copy link
Member

akolson commented Mar 12, 2025

Hi @SukhvirKooner! Any progress on this issue?

@SukhvirKooner
Copy link
Contributor Author

Hi @SukhvirKooner! Any progress on this issue?

Hi @akolson
Yes, I am working on it. I’ve removed the explicit checks for ERR_CANCELED and ECONNABORTED, as well as the manual cancellation on navigation. Currently, I’m testing to ensure that the error doesn’t get reported. I’ll update the PR shortly.

@SukhvirKooner
Copy link
Contributor Author

Hi @akolson
I have successfully removed the redundant checks for isNavigating, error.code === 'ERR_CANCELED', and error.code === 'ECONNABORTED'. The isCancel(error) function already covers all cancellation scenarios, including those triggered by navigation events or manual aborts. This simplifies the error handling logic and reduces unnecessary conditions in the code.

Looking forward to your review and getting this merged!

@MisRob MisRob requested review from akolson and nucleogenesis March 19, 2025 04:41
@SukhvirKooner
Copy link
Contributor Author

Hi,
just checking in to see if there’s been any update on this PR? Let me know if there’s anything needed from my end to help move it forward. Thanks!

@akolson
Copy link
Member

akolson commented Apr 14, 2025

Hi @SukhvirKooner! Could you please rebase this with the latest changes in unstable?

@SukhvirKooner
Copy link
Contributor Author

SukhvirKooner commented Apr 14, 2025

Hi @akolson, I just checked and there haven’t been any updates to the client.js file in the recent PRs to unstable.
Could you let me know if there’s something specific you would like me to rebase or look into?

@akolson
Copy link
Member

akolson commented Apr 15, 2025

Hi @SukhvirKooner ! The goal of the rebase is to keep your branch up to date. Because it's stayed out of unstable for quite a while now (our apologies for that), we are just making sure that all the changes from unstable are brought into your branch and also want to avoid any conflicts that could occur. You might need to force push the changes in your pr.

Also, could you please outline clear steps on how to manually test your PR--unless I missed them, It didn't get the sense that they were included. Could you please be more explicit by adding a "review guidance" section?

@SukhvirKooner
Copy link
Contributor Author

Hi @akolson
I’ve completed the rebase. I'll add a detailed "review guidance" section with manual test instructions shortly.

@SukhvirKooner
Copy link
Contributor Author

Hi @akolson ,
I have added a detailed Review guidance section with step-by-step manual test instructions to help verify that the request abort error is properly suppressed and no longer reported to Sentry.

@akolson
Copy link
Member

akolson commented Apr 22, 2025

@coderabbitai full review

const newAbortController = new AbortController();
abortController.signal = newAbortController.signal;

// Abort pending requests when navigation begins
Copy link
Member

Choose a reason for hiding this comment

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

Lets remove this comment. The code is self explanatory

abortController.abort();
});

// Add request interceptor to track pending requests
Copy link
Member

Choose a reason for hiding this comment

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


// Add request interceptor to track pending requests
client.interceptors.request.use(config => {
// Add signal to the request config
Copy link
Member

Choose a reason for hiding this comment

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

@@ -38,24 +38,59 @@ window.addEventListener('offline', () => {
lastOffline = Date.now();
});

const pendingRequests = new Set();

// Create an AbortController for managing pending requests
Copy link
Member

Choose a reason for hiding this comment

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

pendingRequests.delete(error.config);
}

// Ignore specific types of errors
Copy link
Member

Choose a reason for hiding this comment

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

// Ignore specific types of errors
if (
isCancel(error) ||
(error.response && [302, 403, 404, 405, 412].includes(error.response.status)) // added 302
Copy link
Member

Choose a reason for hiding this comment

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

@SukhvirKooner
Copy link
Contributor Author

I have removed all the mentioned comments. Please let me know if any others still need to be removed.

client.interceptors.response.use(
response => response,
response => {
// Remove from pending requests on success
Copy link
Member

Choose a reason for hiding this comment

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

Lets remove this too!

const status = error.response?.status || 0;

// Suppress contentnode query errors
if (url.includes('contentnode')) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we know status error code the affected url returns?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

aborted requests never actually hit the server, so there is no HTTP status code returned. In the Network tab you’ll just see (canceled), and in our interceptor error.response will be undefined. Axios then falls back to status = 0 and throws a CanceledError with error.code === 'ERR_CANCELED'.

That’s exactly why our suppression logic uses:

if (isCancel(error)) {
  return Promise.reject(error);
}

rather than checking for a particular HTTP response code. Any request you manually abort (or that gets aborted by navigation) will show up as status 0 or ERR_CANCELED, so it’s correctly caught by isCancel(error) and never forwarded to Sentry.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed 💯.
So do we still need to specifically do if (url.includes('contentnode')) { when if (isCancel(error)) { is doing this for us?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes – we still need both guards, because they cover different noise:

  • isCancel(error) catches aborted requests (status 0, ERR_CANCELED)

  • url.includes('contentnode') silences real HTTP errors from our content‑node API that are "safe to ignore"

Removing the contentnode check may flood Sentry with expected CMS glitches, removing isCancel would report every navigation‑cancelled request. We need them both.

But if u think that's still not needed , I'll be happy to remove it.

Copy link
Member

Choose a reason for hiding this comment

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

I actually think it is the same thing based on your explanation 🙂. I think we should remove this check. I also think it is a rigid check -- it would fail for other APIs that don't contain the contentnode path with the same failure scenario.

Copy link
Member

Choose a reason for hiding this comment

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

We can always add this back if sentry says otherwise

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally fair, makes sense to remove it for now. we can always revisit.

@akolson
Copy link
Member

akolson commented Apr 22, 2025

Hi @SukhvirKooner!
Thanks for the reviewer guidance. However, this might be a little difficult to test especially for the QA team (for dev, no problem at all), if we need to manipulate/update the code in the pr.. Don't we have a more straight forward way to test this? (I'm proceeding to test this with the above. Just some food for thought). I also have a question here

@SukhvirKooner
Copy link
Contributor Author

Thanks! This was the approach I came up with to test that the request abort suppression is working as expected. I’ll also look into whether there’s a way to test this without making any changes in the PR code itself.

Copy link
Member

@akolson akolson left a comment

Choose a reason for hiding this comment

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

LGTM!
Thanks and Great work @SukhvirKooner. Sukhvir added some reviewer guidance though I think it it more developer focused. The most effective way for QA to test this is to check the sentry reports (after going through several workflows) to see if any of the suppressed errors show up there again. If they don't we can assume the issue has been resolved. cc @radinamatic @pcenov

@akolson akolson merged commit 26005dc into learningequality:unstable Apr 22, 2025
13 checks passed
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.

Skip reporting some client errors to Sentry
3 participants