All skills
expo avatar

/eas-observe

@948115e official
by expoexpo/skills2.6k stars
156

EAS service (paid). Use for anything related to EAS Observe - adding `expo-observe` to an Expo project (AppMetricsRoot/ObserveRoot HOC, markInteractive and ObserveInteractiveMarker, the useObserve hook, the Expo Router / React Navigation integrations for per-route metrics, user-defined events via `Observe.logEvent`, error reporting via ObserveErrorBoundary and `Observe.reportError`, and runtime config such as sampleRate and dispatchInDebug), querying via the EAS CLI (`eas observe:metrics-summary`, `observe:metrics`, `observe:routes`, `observe:events`, `observe:session`, `observe:versions`), interpreting the resulting metrics (cold/warm launch, TTR, TTI, navigation cold/warm TTR, update download, and the TTI frameRate/device/network params for triaging slow startups), or shipping an Observe integration inside a third-party package.

Use this Skill: https://skilld.dev/gh/expo/skills/eas-observe

This session only. Nothing lands on disk.

referencesmetrics.md

≈2.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

EAS Observe metrics — interpretation cheatsheet

Quick reference for reading EAS Observe dashboards and CLI output.

Source: https://docs.expo.dev/eas/observe/reference/metrics/ — this is the canonical reference for metrics. Consult this page for the latest guidance, full prose definitions, optimization tips, and rationale.

All durations are in seconds. Metric data is retained for a minimum of 60 days. By default, every installation dispatches all of its events; high-volume apps can sample per installation with configure({ sampleRate }) — see Sampling.

Target thresholds

Metric Full name Target Auto-collected?
Cold launch expo.app_startup.cold_launch_time < 1.5s Yes (native-only — JS code does not affect it)
Warm launch expo.app_startup.warm_launch_time < 0.5s Yes (OS decides when warm vs cold happens)
Bundle load expo.app_startup.bundle_load_time < 0.3s Yes (JS load + evaluation, before runApplication)
Time to first render (TTR) expo.app_startup.ttr < 2s incl. cold launch Yes when root is wrapped with AppMetricsRoot (SDK 55) / ObserveRoot (SDK 56+)
Time to interactive (TTI) expo.app_startup.tti < 3s incl. cold launch No — call markInteractive() once the screen is genuinely usable

Both TTR and TTI are measured from native launch through the React render, so the cold-launch portion counts against them.

Interpreting TTI events (automatic params)

Every TTI event carries automatic params in three groups: frame rate, device state, and network state. Read the frame-rate group to classify what kind of slowness you're seeing, then read the device and network groups to decide whether the cause is the code or the conditions.

Frame rate — what kind of slowness

Param Definition What it indicates
expo.frameRate.slowFrames Count of frames ≥ 17ms Main thread consistently busy during launch (heavy layout, sync bridge calls, too many components rendering)
expo.frameRate.frozenFrames Count of frames ≥ 700ms Hard freezes. Even one during startup is a serious issue (sync I/O, large JSON parsing, blocking network)
expo.frameRate.totalDelay Total accumulated time (seconds) frames exceeded their target duration Best single "smoothness" number — compare to TTI

Diagnostic patterns:

  • High TTI + low totalDelay → slow but smooth. The launch sequence itself is long. Optimize bundle size, data-fetch waterfalls, initialization chains.
  • High TTI + high totalDelay + many slowFrames → main-thread contention. Offload work, simplify the initial render tree.
  • High TTI + high totalDelay + any frozenFrames → something is blocking hard. Look for synchronous I/O, large JSON parsing, or blocking network calls.

Device state — is the regression environmental?

Param Type What it indicates
expo.device.lowPowerMode boolean OS power saver was active (Low Power Mode on iOS, Battery Saver on Android). It throttles CPU, GPU, and background work. A regression that disappears when you filter this out is environmental, not a code change.
expo.device.thermalState nominal | fair | serious | critical | unknown Sustained serious/critical means the OS is throttling. Startup slows independently of any app change.
expo.device.batteryLevel number, 0–1 Fractional charge at TTI. Rules out throttling on devices that manage performance aggressively at low charge. Omitted when the OS reports no value.
expo.device.batteryCharging boolean Charging raises sustained CPU ceilings on iOS and some Android OEMs. Non-charging samples are the more conservative population.

Network state — is startup network-bound?

Param Type What it indicates
expo.network.connected boolean If TTI degrades only when true, startup is network-bound. If it degrades when false, the app does too much before showing cached content.
expo.network.type wifi | cellular | ethernet | none | other | unknown Compare cellular against Wi-Fi. A large gap points to network-bound startup work. VPN traffic reports the underlying transport. The value set is identical on both platforms, so dashboards need no per-platform branching.
expo.network.isExpensive boolean Both platforms. The OS considers the connection metered (cellular, hotspot). Present only when a network exists.
expo.network.isConstrained boolean iOS only. Low Data Mode is on for this path, so the system defers background transfers.
expo.network.dataSaverEnabled boolean Android only. Data Saver is on. It is the nearest equivalent of Low Data Mode, but process-wide rather than per-path, hence the separate key.

Network requests — was the network the cause?

TTI events summarize the HTTP requests made during launch, from the end of the native launch to the markInteractive() call. Traffic is observed automatically — URLSession on iOS, OkHttpClient on Android, which covers fetch — and Observe's own uploads are excluded. All of these are omitted when the window held no requests.

Param Unit What it indicates
expo.network.requests.count count Requests that finished in the window. A request still in flight when the app became interactive is not counted anywhere in this table.
expo.network.requests.failed count Errored, returned 4xx/5xx, never got a response, or broke partway through the body. Redirects are not failures.
expo.network.requests.bytesReceived / .bytesSent bytes On-the-wire totals for the window.
expo.network.requests.totalDuration seconds Sum of every request duration, failures included. Exceeds wall-clock when requests overlap; one timeout contributes the client's full timeout interval.
expo.network.requests.throughputBytesPerSecond bytes/sec Received bytes over the time bytes were actually moving (union of transfer windows, measured from each first byte). Excludes DNS, connect, server think time, cache hits, and failures. Requests the OS did not clearly identify as network loads are excluded too. Omitted when nothing was received.
expo.network.requests.slowest.duration seconds The single longest completed request. Requests that never produced a response are excluded, since a timeout measures the client's own setting.
expo.network.requests.slowest.host string Host of that request.
expo.network.requests.slowest.statusCode number Explains an empty response: bytesReceived of 0 is routine on a 304, a problem on a 200.
expo.network.requests.slowest.timeToFirstByte seconds Includes server processing, so treat it as a proxy for network quality, not a measurement of it.
expo.network.requests.slowest.bytesReceived bytes Separates "slow because it moved a lot of data" from "slow while idle".

Diagnostic patterns:

  • slowest.duration mostly timeToFirstByte → the server was slow to answer. Optimize the endpoint, or stop blocking startup on it.
  • Small timeToFirstByte + large bytesReceived → the transfer itself was slow. Shrink the payload or defer it.
  • High failed + high totalDuration → the launch burned time on requests that never arrived. Add timeouts and render cached content first.
  • Low throughputBytesPerSecond on wifi → suspect the population, not the code; cross-check isExpensive and isConstrained / dataSaverEnabled.

The summary is bounded by an in-memory ring buffer of the 200 most recent requests. A launch that makes more undercounts, so read these as a sample of a very busy window.

Custom params

You can attach your own params to the TTI event, and override the route name it is tagged with. See ./setup.md for the call syntax.

Dispatch caveats

  • Debug builds (native debug OR JS bundle with __DEV__ = true) do not dispatch metrics unless configure({ dispatchInDebug: true }) is set.
  • The environment tag (defaults to process.env.NODE_ENV) is metadata only — it does not gate dispatch by itself.
  • Offline events are buffered on-device and flushed when the app backgrounds or Observe.dispatchEvents() is called.

Cross-references

Source: SKILL.md on GitHub

No alerts1mo3 checks · Risk SAFE
  • Gen Agent Trust Hub1mo

    The skill provides comprehensive documentation and implementation guides for EAS Observe, a performance monitoring service for Expo applications. All instructions and tools described are standard components of the Expo development ecosystem.

  • Socket1mo

    No alerts

  • Snyk1mo

    Risk: LOW · No issues

Signed by skilld at 948115e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 2 months ago
version
1.1.0

README badge

README badge for expo/skills/eas-observe