Skip to content

[$175] [Sentry: APP-NAE] HybridApp iOS OldDot: report.js throws "Provided JSON doesn't contain cached data" for support ticket reports #104747

Closed

Description

@mountiny

Sentry

https://expensify.sentry.io/issues/APP-NAE

Impact (snapshot at filing)

  • Users (total since first seen): 2
  • Events: 8
  • Users (last 14d): 2, all on 9.5.13-6
  • First seen: 2026-10-21 (new)
  • Last seen: 2026-10-21 (ongoing)
  • Platform: HybridApp iOS, OldDot (Mobile-Expensify) code
  • App version(s): 9.5.13-6
  • Mechanism: nsexception from -[YAPL logException:], level fatal

Stack trace (OldDot JS, top frames)

Error: Provided JSON doesn't contain cached data
  Report             app/libs/report.js:82
  dataAdapterFunc    app/expensify.js:3292
  (anonymous)        app/libs/apiDeferred.js:252
  execute            app/utils/deferred.js:69
  (anonymous)        app/libs/network.js:62

Suspected cause

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.

Related

  • Sibling Sentry IDs: none confirmed
  • Prior GH issues: none found
Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @nkdengineer

Activity

  1. self-assigned this
    on Oct 22, 2026
  2. added
    ExternalAdded to denote the issue can be worked on by a contributor
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 22, 2026
  3. melvin-bot commented on Oct 22, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @nkdengineer (External)

  4. changed the title [-][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[/+] on Oct 22, 2026
  5. melvin-bot commented on Oct 22, 2026

    @melvin-bot
  6. MelvinBot commented on Oct 22, 2026

    @MelvinBot
    Contributor

    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 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:

    1. Make OldDot skip or tolerate report types it doesn't support.
    2. 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.

  7. Abdallah-1551 commented on Oct 22, 2026

    @Abdallah-1551

    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 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.

    1. 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.
    2. 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.

    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.

    const UNSUPPORTED_REPORT_TYPES = ['supportTicket'];
    
    function safeBuildReports(rawReports) {
        const result = [];
        _.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(new Report(raw));
            } catch (e) {
                // Don't let one bad report take down the whole response
                Sentry.captureException(e, {
                    level: 'warning',
                    extra: {reportID: raw && raw.reportID, type: raw && raw.details && raw.details.type},
                });
            }
        });
        return result;
    }

    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.

    2. Backend (Web-Expensify / API): don't send supportTicket reports 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)

    • 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

    1. 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.
    2. 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.
    3. 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.
    4. 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.
  8. jasperhuangg commented on Oct 23, 2026

    @jasperhuangg
    Contributor

    fix was CP'd to production

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions