{{ message }}
Repository navigation
50949 frontend [ sentry ] handle expired or missing device token on reset - #403
Open
Maria-Lordwill wants to merge 2 commits into
Open
Maria-Lordwill wants to merge 2 commits into
Maria-Lordwill wants to merge 2 commits into
Conversation
Maria-Lordwill
requested review from
EBirkenfeld,
aicarma-artyom-maslov,
pneumatic-ilyacedrik,
pneumojoseph and
timofey-zeph
October 16, 2026 05:38
pneumatic-ilyacedrik
requested changes
Oct 19, 2026
pneumatic-ilyacedrik
approved these changes
Oct 20, 2026
timofey-zeph
approved these changes
Oct 26, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

1. Release notes
Fixed an issue during logout or tenant switching where the device push token was previously deleted or expired on the server. The logout flow and workspace switching now complete cleanly without background errors.
2. Description (problem)
When logging out (Logout) or switching tenant (Switch Tenant,
/tenants/page), the client sends aDELETE /notifications/device/<token>request to detach the browser push token from the current user.If the token was already automatically deleted on the backend (for example, when Google Firebase returned
UnregisteredErrororInvalidArgumentErrorduring push delivery, prompting the server to delete the device record without notifying the client), or if a token from another user session remained inlocalStorage, the backend returns standard DRF404 Not Found({"detail":"Not found."}).Previously, the global Axios interceptor (
commonRequest) unconditionally logged any non-2xx response as an error (logger.error), triggering false positive issues in Sentry.Where it occurred: on the
/tenants/page when switching organizations, and during user logout (Logout).3. Context
pneumatic-frontend-prodprodhttps://my.pneumatic.app/tenants/4. Solution
expectedErrorStatuses) incommonRequest.expectedErrorStatuses, the interceptor logs it as an informational message (logger.info), preventing false error events in Sentry. The HTTP status is not masked as 200 OK — transport contract integrity is preserved.404as expected for device token reset (expectedErrorStatuses: [404]) inresetFirebaseDeviceToken.resetFirebaseDeviceTokeninfirebase/utils.tsasynchronous: the local token is now guaranteed to be removed fromlocalStorage(localStorage.removeItem(FIREBASE_DEVICE_STORAGE_KEY)) after the API call, and the logout saga now waits for it viayield call(resetFirebaseDeviceToken).5. Implementation details
commonRequest.ts):isExpectedClientError(responseData)was logged withlogger.error, generating a Sentry event.expectedErrorStatuses?: number[]toICommonRequestOptions; the matchingAxiosRequestConfigaugmentation lives intypings/axios.d.ts(see item 7). When the response status matches an entry in the array, the error is logged aslogger.info.resetFirebaseDeviceToken.ts):{ shouldThrow: false, responseType: 'empty' }.expectedErrorStatuses: [404].firebase/utils.ts):resetFirebaseDeviceTokenAPIwas called synchronously without awaiting response, andlocalStoragewas never cleared.async: it awaitsawait resetFirebaseDeviceTokenAPI(token)and then executeslocalStorage.removeItem(FIREBASE_DEVICE_STORAGE_KEY).redux/auth/saga.ts):resetFirebaseDeviceToken().yield call(resetFirebaseDeviceToken).6. What to test
6.1 Preconditions
local)./tenants/).6.2 Positive scenarios
firebase_device_tokenexists inlocalStorage. Click Logout. Expected: aDELETE /notifications/device/<token>request is sent in Network and returns 204. Token is removed fromlocalStorage. User is redirected to login page./tenants/and switch to another organization. Expected: tenant switch completes successfully and redirects without UI errors.6.3 Negative scenarios and edge cases
firebase_device_tokeninlocalStorageto a fake valuedeleted_token_404. Click Logout. Expected:DELETE /notifications/device/deleted_token_404returns HTTP 404{"detail":"Not found."}. The response is logged to console asinfo(no rederror). No Sentry event is created on staging. Token is removed fromlocalStorage. Logout completes successfully.DELETE /notifications/device/<token>. Expected: HTTP 500 is not inexpectedErrorStatuses, so it is logged vialogger.errorand sent to Sentry. Due toshouldThrow: false, the user logout flow is not interrupted.expectedErrorStatuses). Expected: error is logged vialogger.errorand sent to Sentry as usual.firebase_device_tokenkey inlocalStorageand click Logout. Expected: no token deletion request is sent, logout succeeds normally without errors.6.4 Verification points
/notifications/device/<token>, no newResponse Error: 404 {"detail":"Not found."}events are created.infolevel, noterror.DELETE /notifications/device/<token>request is made and retains its 404 status (backend HTTP contract is not masked).firebase_device_tokenkey is removed fromlocalStorageunder both outcomes (204 success or 404 not found).6.5 API checks
DELETE/api/v2/notifications/device/<token>/(where<token>is the value fromlocalStorage.firebase_device_token).{"detail":"Not found."}.6.6 What was NOT tested
7. Refactoring
The refactoring keeps type declarations in one place of the project (
src/typings) instead of scattering them across application code.Axios type declaration for
expectedErrorStatuses.AxiosRequestConfigaugmentation with theexpectedErrorStatusesfield was declared directly incommonRequest.ts(declare module 'axios'). It was moved to a separate file,frontend/src/typings/axios.d.ts, next to the other type declaration files of the project.commonRequest.tsnow has animport type {} from '../../typings/axios', which makes the augmentation visible tots-node(it ignoresincludefromtsconfig, as noted in the code comment). Application behavior does not change — only the type declaration was moved.frontenddirectory runnpm run build-client:prod. Expected: the build finishes without TypeScript and webpack errors.frontenddirectory runnpm run local. Expected: the server starts without TypeScript errors and the application page opens.expectedErrorStatusesstill worksinfo, no Sentry event is created.8. Testing of affected places
Changes touched the global
commonRequest.tsinterceptor used by all API requests in the application, and the logout saga inredux/auth/saga.ts.logger.errorcall and dispatch to Sentry).9. Unit tests
The following scenarios are covered:
expectedErrorStatuses: [404]upon receiving 404 invokeslogger.infowithout callinglogger.error.expectedErrorStatusesupon receiving 404 continues callinglogger.error.resetFirebaseDeviceToken(token)sends HTTP methodDELETE, correct URL/notifications/device/<token>, and options{ shouldThrow: false, responseType: 'empty', expectedErrorStatuses: [404] }.resetFirebaseDeviceToken()reads token fromlocalStorage, calls reset API, and removes token fromlocalStorage.localStorage, no API request is sent.All tests were validated using TDD (failed with
Expected logger.info / Received logger.erroron unpatched code, pass cleanly on patched code).10. Commits
5c9699071—50949 fix(api): ignore 404 on device token reset1c09b2fca—50949 refactor(api): move axios type to typingsCloses https://pneumatic.sentry.io/issues/PNEUMATIC-FRONTEND-PROD-24V
Note
Medium Risk
Touches the global Axios response interceptor used by all API calls, though behavior only changes when callers opt in via expectedErrorStatuses; logout timing now waits on token reset.
Overview
Fixes false Sentry noise when DELETE device token returns 404 during logout or tenant switch.
commonRequestgains optionalexpectedErrorStatuses: matching HTTP statuses are logged withlogger.infoinstead oflogger.error(responses still reject as before).resetFirebaseDeviceTokenpasses[404]for already-missing tokens.Firebase reset flow is now async: it awaits the DELETE, then
localStorage.removeItemfor the device token key (replacing clearing to an empty string). The logout sagayield call(resetFirebaseDeviceToken)so cleanup finishes before the rest of sign-out.Unit tests cover interceptor logging, the API client options, and Firebase util behavior.
Reviewed by Cursor Bugbot for commit 1c09b2f. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
[!NOTE]
Treat expired or missing device token on logout as expected, not an error
firebase.resetFirebaseDeviceTokenin utils.ts async: it awaits the DELETE API call before updating localStorage, and removes the Firebase token key instead of storing an empty string. The logout saga in saga.ts now yields this call.expectedErrorStatusesarray toAxiosRequestConfigandICommonRequestOptionsin commonRequest.ts. The response interceptor logs matching statuses withlogger.infoinstead oflogger.error; rejection and API error creation are unchanged.expectedErrorStatuses: [404], so a missing token on logout is no longer logged as an error. Adds test coverage for the interceptor, API clients, and Firebase utilities.firebase.resetFirebaseDeviceTokenis now async and callers that don't await it will see timing differences; logout waits for the API call before clearing superuser tokens and analytics.Changes since #403 opened
AxiosRequestConfigtype augmentation to dedicated ambient declaration file [1c09b2f]Macroscope summarized 5c96990.