Skip to content

Inspector proxy device heartbeat (PR #49618) terminates debug sessions when iOS app is backgrounded #56614

@HerrNiklasRaab

Description

@HerrNiklasRaab

Summary

Since RN 0.79, HEARTBEAT_TIMEOUT_MS = 60_000 in @react-native/dev-middleware's InspectorProxy.js causes Metro to terminate the device WebSocket when the iOS app is backgrounded for ~60 seconds, killing the active debug session. This is a regression introduced by #49618.

Environment

  • React Native: 0.81.5
  • @react-native/dev-middleware: 0.81.5
  • iOS device (real, not simulator)
  • Hermes engine

Reproduction

  1. Run a debug session against an iOS device build (e.g. via vscode-react-native extension's reactnativedirect attach)
  2. Confirm Hermes is connected and breakpoints work
  3. Switch from the app to another app on the device (or send to background)
  4. Wait ~60 seconds
  5. Debug session terminates

Root cause

  • iOS suspends the app's JS thread shortly after backgrounding (standard iOS behavior)
  • Hermes can no longer respond to WebSocket Ping frames sent by Metro
  • Metro's InspectorProxyHeartbeat fires socket.terminate() after 60s timeout
  • Device.js close handler emits [CONNECTION_LOST] Connection lost to corresponding device to debugger
  • VSCode debug session terminates

Evidence

TCP-level packet capture shows the iPhone's TCP stack remains responsive throughout (sending ACKs to all data Metro pushes), proving the network connection itself is not lost — only the application-level Pong is missing because Hermes is suspended:

Time Event
t=0s iOS app sent to background
t=0–60s Mac sends Text + Ping frames; iPhone replies with TCP ACKs but no WS Pong
t=~60s Metro fires socket.terminate(), sends FIN
t=~60s Inspector proxy emits CONNECTION_LOST to debugger

Why this is a regression

The PR #49618 description explicitly states:

"It is a no-op because the WebSocket on the Device is implemented by us and is not supposed to drop connections like the browser does."

This assumption doesn't hold for iOS apps where the OS pauses the JS thread on backgrounding. Pre-#49618 setups did not terminate the device socket, so debugging survived backgrounding/foregrounding cycles.

Proposal

Options (any/all):

  1. Make HEARTBEAT_TIMEOUT_MS configurable via Metro config or env var
  2. Default to a much higher timeout in dev mode (e.g. 24h) — the original goal of cleaning up "abandoned" remote devices is still met if the dev never returns
  3. Treat ping timeout as a "stale" marker without actively terminating, allowing reconnect when the device wakes
  4. Detect iOS app lifecycle hints (if available) and pause the heartbeat while the device is suspended

The current behavior breaks common dev workflows (switching to other apps to test deep links, payments, OAuth, etc.).

Workaround

Patch HEARTBEAT_TIMEOUT_MS via patch-package / bun patch to a high value (e.g. 24h).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: AttentionIssues where the author has responded to feedback.Needs: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions