Repository navigation
[Sentry: APP-M2G / APP-M0D / APP-M5M / APP-M4R] HybridApp crash, CONST.VALIDATE_CODE_REASONS undefined in card fraud and card replacement flows #102045
Description
If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.
Version Number: 9.4.89
Reproducible in staging?: Needs to be confirmed
Reproducible in production?: Yes — crash is live in production
If this was caught during regression testing, add the test name, ID and link from BrowserStack: N/A — found via Sentry production crash scan
Email or phone of affected tester (no customers): N/A
Logs: https://stackoverflow.com/c/expensify/questions/4856
Expensify/Expensify Issue URL:
Issue reported by: Sentry production crash scan
Slack conversation (hyperlinked to channel name): N/A
Action Performed:
- Open Expensify Classic on iOS or Android (HybridApp, OldDot side).
- Go to an Expensify Card.
- Start either flow:
- Report the card as fraudulent, which opens the request replacement virtual card modal.
- Replace the card, which opens the card replacement step.
Expected Result:
The modal opens and asks for a magic code. No crash.
Actual Result:
The app crashes as the modal finishes its present animation. Two variants, one per flow:
TypeError: undefined is not an object (evaluating 'CONST.VALIDATE_CODE_REASONS.REPORT_CARD_FRAUD')
at app/Controllers/Modals/RequestReplacementVirtualCardModalController.js:79:48
TypeError: undefined is not an object (evaluating 'CONST.VALIDATE_CODE_REASONS.REPLACE_CARD')
at app/Controllers/CardReplacementStepController.js:147:56
CONST.VALIDATE_CODE_REASONS is undefined at that point, so reading a member off it throws and the surrounding native modal completion turns it into a fatal exception.
Workaround:
None. Both card flows are blocked on mobile OldDot for affected users.
Platforms:
Select the officially supported platforms where the issue was reproduced:
- Android: App
- Android: mWeb Chrome
- iOS: App
- iOS: mWeb Safari
- iOS: mWeb Chrome
- Windows: Chrome
- MacOS: Chrome / Safari
Screenshots/Videos
Sentry details
Sentry issues:
| Sentry ID | Users (7d) | Events (7d) | Platform | Constant |
|---|---|---|---|---|
| APP-M2G | 6 | 7 | iOS | REPORT_CARD_FRAUD |
| APP-M0D | 4 | 9 | iOS | REPLACE_CARD |
| APP-M5M | 1 | 1 | Android | REPORT_CARD_FRAUD |
| APP-M4R | 1 | 2 | Android | REPLACE_CARD |
Impact (snapshot at filing, 2026-09-23):
- Users (last 7d, all four IDs): 12
- Events (last 7d, all four IDs): 19
- First seen: 2026-09-17
- Last seen: ongoing
- Platform: HybridApp iOS (
com.expensify.expensifylite) and Android (org.me.mobiexpensifyg) - Mechanism:
nsexceptionvia-[YAPL logException:]on iOS,JSCWrapperon Android; levelfatal
Stack trace — APP-M2G (iOS, innermost last):
BottomDrawerModalViewController.postViewDidLoad
GenericTwoChoiceModalViewController.postViewDidLoad
BottomDrawerModalViewController.beginLeaveAnimation
+[YAPLJS callFunction:args:]
-[JSValue callWithArguments:]
-[JSContext(Internal) valueFromNotifyException:]
-[YAPL logException:]
app/Controllers/Modals/RequestReplacementVirtualCardModalController.js:79:48
Suspected cause: a regression from [reasonCode] Pass card and validate reasons from Mobile-Expensify, which closed on 2026-09-15. The first crash event is 2026-09-17. That work introduced reads of CONST.VALIDATE_CODE_REASONS.* in these two controllers, but the VALIDATE_CODE_REASONS map does not exist on CONST in the shipped Mobile-Expensify bundle.
Likely one of: the constant was added to a file the mobile bundle does not include, it is defined under a different name, or it is populated asynchronously and read before it lands.
Note: the fix lives in Mobile-Expensify (Expensify Classic), not in the App repo. The parent issue carries Concierge (dont reopen), so this is filed fresh rather than reopening it.
Issue Owner
Current Issue Owner: @mountinyMetadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
melvin-bot commentedon Sep 23, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @linhvovan29546 (
External)melvin-bot commentedon Sep 23, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
MelvinBot commentedon Sep 23, 2026
Proposal
What is the root cause of that problem?
VALIDATE_CODE_REASONSis owned byexpensify-common, not by the globalCONSTobject that Expensify Classic's native JS runtime exposes. The two Classic card controllers read a bare globalCONST, so they resolve Classic's own constants object — which has noVALIDATE_CODE_REASONSkey — andundefined.REPORT_CARD_FRAUDthrows.The JSC error text confirms this precisely:
undefined is not an object (evaluating 'CONST.VALIDATE_CODE_REASONS.REPORT_CARD_FRAUD')meansCONSTis an object andCONST.VALIDATE_CODE_REASONSisundefined. A missingCONSTwould instead raiseCan't find variable: CONST.The map lives at
lib/CONST.ts:903-913inexpensify-common, withREPLACE_CARD: 'replace_card'andREPORT_CARD_FRAUD: 'report_card_fraud'.Nothing in the App repo causes this, and nothing in the App repo can fix it. NewDot reaches the same constants through a module import, never a global, so its equivalent flows are unaffected.
What changes do you think we should make in order to solve the problem?
The fix has to land in Expensify Classic (Mobile-Expensify), which I don't have access to — so I can't propose a concrete patch. Please escalate to an internal engineer with access. The shape of the fix is:
expensify-common'sCONSTrather than the Classic globalCONST, or add theVALIDATE_CODE_REASONSmap to Classic's own constants.expensify-commonversion Classic pins actually containsREPORT_CARD_FRAUD— it was added after the rest of the map, so an older pin would leave the keyundefinedeven with a correct import.reasonCodeinstead of throwing inside the modal's present-animation completion, which is what turns this into a fatal native exception rather than a caught JS error.Worth checking during the fix: Auth tolerates a validate code with no
reasonCode, but rejects one generated with the wrongreasonCode— so a fallback must omit the field, not substitute a placeholder.What alternative solutions did you explore? (Optional)
Alternatives considered and ruled out
expensify-common@2.0.201is pinned in App and contains the full map; every App consumer imports it explicitly asCOMMON_CONST, for exampleReportVirtualCardFraudVerifyAccountPage.tsx:83andReportCardLostConfirmValidateCodePage.tsx:95.modules/hybrid-app/passes only strings, booleans, and a three-key Onyx settings blob. No constants object crosses, and App never assignsglobal.CONST/window.CONSTor injects JS into a Classic webview. The two sides are separately bundled with separateCONSTobjects.CONSTshould carry the map. Ruled out.ResendValidateCodeParams.tsdocuments in-code thatexpensify-common'sVALIDATE_CODE_REASONSis the canonical home; App keeps exactly one reason locally and explicitly notes it's temporary.Related issues: I scanned open App issues for this crash signature and the surrounding card/magic-code flows and found no duplicates or near-matches.
Verification note: I could not drive a browser for this run, and this crash is native-only on the Classic side, so the analysis is from code and the Sentry data in the description rather than a live repro. The
expensify-commonversion Classic pins is the one claim I could not check directly.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102045)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
mountiny commentedon Sep 23, 2026
(Vit's agent)
Root cause found, draft fix up at Expensify/expensify-common#948.
Why this crashes.
CONSTreaches OldDot throughapp/shared/index.js, which doesrequire('expensify-common/lib/CONST').CONST. Mobile-Expensify pins expensify-common at45c36bfon thechirag-mobile-oldbranch — the v1 compatibility line for OldDot. That branch forked frommainat2e5cff55on 2021-05-10, andmainhas since migratedCONSTto TypeScript (lib/CONST.jsx→lib/CONST.ts, commite85751d1, 2024-05-17), so OldDot cannot consume the v2 tree.VALIDATE_CODE_REASONSwas added ine06391eon 2026-04-28, tomainonly. It was never back-ported to v1. The reasonCode work then added three call sites in Mobile-Expensify against a constant that does not exist on the branch OldDot actually installs:app/Controllers/Modals/RequestReplacementVirtualCardModalController.jsREPORT_CARD_FRAUDapp/Controllers/CardReplacementStepController.jsREPLACE_CARDapp/Controllers/InboxController.jsVALIDATE_ACCOUNTEvery other
CONST.*member Mobile-Expensify uses (DATE,STATES,SMART_SCAN,EXPENSIFY_CARD) does exist in the pinnedCONST.jsx. Only this one is missing, which matches the crash exactly.Note the third call site,
InboxController.js:2445(VALIDATE_ACCOUNT, resend validation code from the inbox task), is the same bug and is not in the Sentry issues on this ticket yet. The fix covers it.Fix, in two steps. This follows the established v1 back-port workflow, the same as expensify-common #932 and #944.
VALIDATE_CODE_REASONSmap tolib/CONST.jsxonchirag-mobile-old. Values diffed key by key againstmain; all 14 identical. Draft, ready for review.package-lock.jsoncarries anintegrityhash that onlynpm installcan compute for the new sha.No code change is needed in Mobile-Expensify itself — the three call sites are correct, the constant they read just was not there.
mountiny commentedon Sep 23, 2026
PR here https://github.com/Expensify/Mobile-Expensify/pull/14141
melvin-bot commentedon Sep 30, 2026
@mountiny Whoops! This issue is 2 days overdue. Let's get this updated quick!
mountiny commentedon Oct 1, 2026
@MelvinBot can you check Sentry for this event if this is still repro in the latest versions?
MelvinBot commentedon Oct 1, 2026
I couldn't check Sentry. The Sentry connection was rejected for this run (HTTP 401, invalid token), so I couldn't query APP-M2G, APP-M0D, APP-M5M, or APP-M4R.
What I could confirm: the Add VALIDATE_CODE_REASONS to v1 CONST fix merged on 2026-09-23. I can't see whether the follow-up Mobile-Expensify bump has merged or which App version ships it.
To confirm the fix, filter each Sentry issue by
releaseand check for events in versions after the Mobile-Expensify bump. Retry@MelvinBotonce the Sentry token is fixed and I'll check it.view run
mountiny commentedon Oct 2, 2026
(Vit's agent)
Checked Sentry. The crash is fixed in every App release that ships the fix.
7ac02f2) both merged on 2026-09-23.Events for APP-M2G, APP-M0D, APP-M5M and APP-M4R in the last 30 days:
The fixed releases have real traffic: 3,897 mobile error events from 2,622 users in the last 14 days, and none of them are this crash. The last event came on 2026-09-28 from 9.4.92-8, which does not include the fix. A few more events can still come from users on old builds.
Closing this issue as fixed.