You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] [Sentry: APP-NAE] HybridApp iOS OldDot: report.js throws "Provided JSON doesn't contain cached data" for support ticket reports #104747
The OldDot Report model throws when a report has an empty cachedData. The crashing reports have details.type set to supportTicket, state MANUALREIMBURSED, and transactionCount: 0. The API returns these reports to OldDot, and the OldDot model does not handle this report type. The fix is in Mobile-Expensify (app/libs/report.js), or in the API so it does not send these reports to OldDot.
Reproduction
Unknown. See the Sentry events linked from the Sentry issue. All events come from accounts with a support ticket report.
The fix is outside the App repo. The crash happens in OldDot (Expensify Classic) code inside HybridApp iOS, and it is triggered by data the backend sends to OldDot. Neither part lives in Expensify/App, so an outside contributor can't fix this. An internal engineer with access to those repos should take it.
Root cause: Support ticket reports are a newer NewDot-only report type (supportTicket). NewDot handles them, for example in src/CONST/index.ts:2360, which sits behind the supportTicket beta at src/CONST/index.ts:1434. OldDot does not know this report type. When OldDot's own API call returns one of these reports, OldDot's report model throws Provided JSON doesn't contain cached data and the app crashes.
Recommended next step: Escalate to an internal engineer. Two ways to fix it, both outside App:
Make OldDot skip or tolerate report types it doesn't support.
Stop the backend from sending support ticket reports to OldDot.
Since a contributor can't fix this, consider removing External and the Upwork job.
Investigation details
The stack trace starts in OldDot's network layer (network.js → apiDeferred.js → dataAdapterFunc). OldDot made the request and parsed the response itself. No NewDot code runs in this path.
Every affected account has a support ticket report (supportTicket, MANUALREIMBURSED, transactionCount: 0). That fits a report type OldDot doesn't support.
Scale is small so far: 2 users and 8 events, all on 9.5.13-6, first seen 2026-10-21. That timing fits support ticket reports reaching more accounts recently.
In App, support ticket handling is NewDot UI only (SupportTicketView, SupportTicketPreview, ReportUtils). Nothing in App decides what OldDot gets from the API.
I couldn't read the OldDot source or the Sentry events from here. Confidence: medium. The cause comes from the stack trace and event data in the issue body, not from reading OldDot code.
Next Steps: Reply with @MelvinBot <your feedback> to refine this analysis.
Please re-state the problem that we are trying to solve in this issue.
HybridApp iOS (9.5.13-6) crashes with a fatal exception in the OldDot (Mobile-Expensify) code. OldDot's Report model (app/libs/report.js:82) throws Provided JSON doesn't contain cached data when it builds a model from an API response. This happens for accounts with a supportTicket report (state MANUALREIMBURSED, transactionCount: 0). The exception is uncaught, so it reaches -[YAPL logException:] and the whole app crashes. This affects at least 2 users and 8 events so far, and more accounts are likely to hit it as support tickets roll out.
What is the root cause of that problem?
There are two causes, and the second makes the first fatal.
The API returns a report type OldDot doesn't know about.supportTicket is a NewDot-only report type, gated by the supportTicket beta (CONST.BETAS) in Expensify/App. The backend still includes these reports in the OldDot response. They have no cachedData, which is plausible because they have no expenses (transactionCount: 0) and OldDot's report-cache pipeline doesn't run for them.
OldDot has no defensive handling.dataAdapterFunc (expensify.js:3292) constructs a Report for every item with no type filter and no try/catch. The constructor throws when cachedData is empty. The throw propagates through apiDeferred.js → deferred.js → network.js and surfaces as a fatal native exception. One unsupported report therefore breaks the entire response.
I could not read the OldDot source or the Sentry events, so I inferred the missing-cachedData cause from the stack trace and event data. Confidence is medium.
Nothing in Expensify/App participates in this path, so a change there cannot fix it.
What changes do you think we should make in order to solve the problem?
This needs an internal engineer with access to Mobile-Expensify and the backend. Remove the External label and the Upwork job. I recommend a defensive fix in OldDot, plus a backend filter as the root fix.
In app/expensify.js (dataAdapterFunc, around line 3292), use code like the following. The names are illustrative, so adapt them to the real code.
constUNSUPPORTED_REPORT_TYPES=['supportTicket'];functionsafeBuildReports(rawReports){constresult=[];_.each(rawReports,(raw)=>{if(_.contains(UNSUPPORTED_REPORT_TYPES,raw&&raw.details&&raw.details.type)){return;// NewDot-only report type, OldDot does not render it}try{result.push(newReport(raw));}catch(e){// Don't let one bad report take down the whole responseSentry.captureException(e,{level: 'warning',extra: {reportID: raw&&raw.reportID,type: raw&&raw.details&&raw.details.type},});}});returnresult;}
In app/libs/report.js, the empty-cachedData check can stay as a guard, but callers must handle it. Do not downgrade it to a silent default, because other report types rely on that invariant.
In the command or serializer that builds OldDot's report list, exclude reports whose type is supportTicket. Key this on the OldDot client or platform flag, so NewDot still receives them. This prevents the same problem for other future NewDot-only types. A whitelist of types OldDot supports is safer than a blacklist.
3. Follow-up. Add a Sentry breadcrumb or metric for skipped reports, so we can see whether other unknown types are reaching OldDot.
What alternative solutions did you explore? (Optional)
Add supportTicket support to OldDot (generate cachedData and render it): Rejected. It is a large effort for a type that is intentionally NewDot-only.
Fix in Expensify/App: Rejected. OldDot makes the request and parses the response itself, so App code is not involved.
Only a global try/catch around the network layer: Rejected. It would hide real failures and drop valid reports in the same response. Per-report isolation is more precise.
Backend-only fix: Insufficient on its own. Older OldDot builds and any future unknown type would still crash, so the client-side guard is needed as well.
Testing
Unit tests (Mobile-Expensify): Feed dataAdapterFunc a response containing a supportTicket report (MANUALREIMBURSED, transactionCount: 0, empty cachedData) alongside a normal report. Assert that there is no throw, the normal report is built, and the support ticket is skipped. Add a case where a normal-type report has empty cachedData and assert that it is caught and logged without breaking the others.
Manual (HybridApp iOS): Use an account with a support ticket report. Before the fix, OldDot crashes on the API call. After the fix, the app loads and the reports list shows the other reports.
Backend: Verify that OldDot-originated requests no longer include supportTicket reports, and that NewDot requests still receive them and show them in SupportTicketView and SupportTicketPreview.
Monitoring: After release, confirm that Sentry issue APP-NAE stops receiving new events and that the new warning-level events appear at a low, expected rate.
Activity
melvin-bot commentedon Oct 22, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @nkdengineer (
External)[-][Sentry: APP-NAE] HybridApp iOS OldDot: report.js throws "Provided JSON doesn't contain cached data" for support ticket reports[/-][+][$175] [Sentry: APP-NAE] HybridApp iOS OldDot: report.js throws "Provided JSON doesn't contain cached data" for support ticket reports[/+]melvin-bot commentedon Oct 22, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022113270989143992259
MelvinBot commentedon Oct 22, 2026
Issue Analysis
The fix is outside the App repo. The crash happens in OldDot (Expensify Classic) code inside HybridApp iOS, and it is triggered by data the backend sends to OldDot. Neither part lives in
Expensify/App, so an outside contributor can't fix this. An internal engineer with access to those repos should take it.Root cause: Support ticket reports are a newer NewDot-only report type (
supportTicket). NewDot handles them, for example in src/CONST/index.ts:2360, which sits behind thesupportTicketbeta at src/CONST/index.ts:1434. OldDot does not know this report type. When OldDot's own API call returns one of these reports, OldDot's report model throwsProvided JSON doesn't contain cached dataand the app crashes.Recommended next step: Escalate to an internal engineer. Two ways to fix it, both outside App:
Since a contributor can't fix this, consider removing
Externaland the Upwork job.Investigation details
network.js→apiDeferred.js→dataAdapterFunc). OldDot made the request and parsed the response itself. No NewDot code runs in this path.supportTicket,MANUALREIMBURSED,transactionCount: 0). That fits a report type OldDot doesn't support.SupportTicketView,SupportTicketPreview,ReportUtils). Nothing in App decides what OldDot gets from the API.Next Steps: Reply with
@MelvinBot <your feedback>to refine this analysis.Abdallah-1551 commentedon Oct 22, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
HybridApp iOS (9.5.13-6) crashes with a fatal exception in the OldDot (Mobile-Expensify) code. OldDot's
Reportmodel (app/libs/report.js:82) throwsProvided JSON doesn't contain cached datawhen it builds a model from an API response. This happens for accounts with asupportTicketreport (stateMANUALREIMBURSED,transactionCount: 0). The exception is uncaught, so it reaches-[YAPL logException:]and the whole app crashes. This affects at least 2 users and 8 events so far, and more accounts are likely to hit it as support tickets roll out.What is the root cause of that problem?
There are two causes, and the second makes the first fatal.
supportTicketis a NewDot-only report type, gated by thesupportTicketbeta (CONST.BETAS) inExpensify/App. The backend still includes these reports in the OldDot response. They have nocachedData, which is plausible because they have no expenses (transactionCount: 0) and OldDot's report-cache pipeline doesn't run for them.dataAdapterFunc(expensify.js:3292) constructs aReportfor every item with no type filter and no try/catch. The constructor throws whencachedDatais empty. The throw propagates throughapiDeferred.js→deferred.js→network.jsand surfaces as a fatal native exception. One unsupported report therefore breaks the entire response.I could not read the OldDot source or the Sentry events, so I inferred the missing-
cachedDatacause from the stack trace and event data. Confidence is medium.Nothing in
Expensify/Appparticipates in this path, so a change there cannot fix it.What changes do you think we should make in order to solve the problem?
This needs an internal engineer with access to Mobile-Expensify and the backend. Remove the
Externallabel and the Upwork job. I recommend a defensive fix in OldDot, plus a backend filter as the root fix.1. Mobile-Expensify: skip unsupported report types and isolate failures (immediate fix).
In
app/expensify.js(dataAdapterFunc, around line 3292), use code like the following. The names are illustrative, so adapt them to the real code.In
app/libs/report.js, the empty-cachedDatacheck can stay as a guard, but callers must handle it. Do not downgrade it to a silent default, because other report types rely on that invariant.2. Backend (Web-Expensify / API): don't send
supportTicketreports to OldDot (root fix).In the command or serializer that builds OldDot's report list, exclude reports whose type is
supportTicket. Key this on the OldDot client or platform flag, so NewDot still receives them. This prevents the same problem for other future NewDot-only types. A whitelist of types OldDot supports is safer than a blacklist.3. Follow-up. Add a Sentry breadcrumb or metric for skipped reports, so we can see whether other unknown types are reaching OldDot.
What alternative solutions did you explore? (Optional)
supportTicketsupport to OldDot (generatecachedDataand render it): Rejected. It is a large effort for a type that is intentionally NewDot-only.Expensify/App: Rejected. OldDot makes the request and parses the response itself, so App code is not involved.Testing
dataAdapterFunca response containing asupportTicketreport (MANUALREIMBURSED,transactionCount: 0, emptycachedData) alongside a normal report. Assert that there is no throw, the normal report is built, and the support ticket is skipped. Add a case where a normal-type report has emptycachedDataand assert that it is caught and logged without breaking the others.supportTicketreports, and that NewDot requests still receive them and show them inSupportTicketViewandSupportTicketPreview.jasperhuangg commentedon Oct 23, 2026
fix was CP'd to production