Skip to content

React Native 0.86 iOS traces are backdated after device sleep #6568

Description

@fkreinh

Environment

  • Expo SDK 57
  • React Native 0.86.2 (regression began after 0.85.3 → 0.86.0)
  • Hermes on iOS
  • Reproduced with @sentry/react-native 8.21.0 / @sentry/core 10.69.0
  • Production regression was first observed with @sentry/react-native 8.16.0
  • @sentry/react-native 8.22.0 still depends on the same @sentry/core 10.69.0
  • tracesSampleRate: 1.0
  • sentry.io SaaS

Summary

Transactions are processed, uploaded, and accepted by Sentry with HTTP 200, but their timestamps are backdated by the amount of time the Apple performance clock did not advance during system sleep.

New iOS traces therefore disappear from current Sentry time windows. In production this looked like an abrupt iOS tracing-volume drop immediately after upgrading to Expo SDK 57 / React Native 0.86.

This is related to #6510, but that report is scoped to structured logs and says tracing uses a guarded time-origin path. React Navigation and app-start transactions are also affected because span creation and completion use timestampInSeconds() directly.

Steps to reproduce

  1. Use Expo SDK 57 / React Native 0.86 on iOS with Hermes.
  2. Allow the device or simulator host to accumulate sleep time since boot.
  3. Initialize Sentry with tracesSampleRate: 1 and debug: true.
  4. Compare Date.now() with performance.timeOrigin + performance.now().
  5. Start a navigation or app-start transaction.
  6. Observe the native transport writing the envelope and receiving HTTP 200.
  7. Fetch the transaction by exact event ID through the Sentry API and compare dateReceived with startTimestamp.

Observed clocks

Date.now():                         1786036083636
performance.timeOrigin:            1784904816638.1536
performance.now():                 949785977.287708
timeOrigin + performance.now():    1785854602615.4412
skew:                              181481020.56 ms (~50.4 hours)

One transaction was accepted as:

dateReceived:      2026-08-06T17:08:10.006329Z
startTimestamp:    2026-08-04T14:43:24.963859Z
environment:       development
os.name:           iOS
clientSampleRate:  1.0

The event could be fetched by exact ID. It was absent from current trace views because it was indexed roughly 50 hours in the past.

Regression boundary

React Native 0.86 promoted Web Performance APIs to stable/default-on:

react/react-native@beccee2

Before that change, Sentry effectively fell back to Date.now() in our stable React Native runtime. Once performance.timeOrigin became available, @sentry/core selected the Performance-based clock.

Root cause

@‌sentry/core createUnixTimestampInSecondsFunc() selects the clock once and trusts performance.timeOrigin + performance.now() without validating it against wall-clock time.

React Native's Apple high-resolution timer uses mach_absolute_time(). That clock does not include system sleep, so the absolute timestamp drifts behind Date.now().

The same Core time module validates this relationship in getBrowserTimeOrigin(), but timestampInSeconds() does not use that validation.

Related Sentry issue: getsentry/sentry-javascript#2590

Expected result

New transactions should be indexed near their actual wall-clock time and appear in current trace windows after successful ingestion.

Actual result

Transactions are ingested successfully but indexed hours or days in the past. Reinstalling or restarting the app does not repair the system-boot-based clock skew.

Verified workaround

We patched @sentry/core so createUnixTimestampInSecondsFunc() consistently returns dateTimestampInSeconds() when navigator.product === "ReactNative".

With that JS-only patch, while the Performance clock remained ~50.4 hours behind, an app.start transaction was received and indexed at the current time:

dateReceived:      2026-08-06T17:20:04.665104Z
startTimestamp:    2026-08-06T17:19:39.070688Z
operation:         app.start
os.name:           iOS
clientSampleRate:  1.0

A dynamic sanity check with fallback to Date.now(), or an explicit React Native clock choice, should prevent traces from disappearing from current time queries.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions