Skip to content

Using rn-dev-agent with maestro-mcp

rn-dev-agent and the standalone maestro-mcp server can be used together: maestro executes resilient flows, rn-dev-agent verifies internal state. They share one iOS simulator, so this page describes how they coexist.

  • L1 introspection (CDP reads)cdp_navigation_state, cdp_store_state, cdp_component_tree, etc. — is always safe. It reads Hermes over CDP and never touches the XCUITest automation channel maestro uses.
  • L2 interaction (device_*) shares the XCUITest channel with maestro’s iOS driver. After a maestro run, the next device_* may find the channel evicted.

What happens on handback (already automatic)

Section titled “What happens on handback (already automatic)”

When an L2 call sees the runner-leak sentinel after a maestro run, the device-session performs a state-preserving reacquire — it re-foregrounds your app (no relaunch) and marks CDP stale so the next read transparently reconnects. You do not need a manual cdp_status re-pin (shipped in #188).

When you device_snapshot action=open while a foreign maestro automation session is driving the same simulator (and rn-dev-agent is not itself running a flow), the open result carries an informational meta.foreignRunner and a FOREIGN_RUNNER_ACTIVE warning. The open itself is never blocked.

Since #186 shipped, the contention handling goes further: while a foreign session is live (UDID-scoped detection, ~5 s cache), rn-dev-agent’s device_* taps and maestro_run flows refuse fast with BUSY_FOREIGN_FLOW (~50 ms) instead of colliding into the multi-second runner-leak recovery cascade. L1 reads (cdp_component_tree, cdp_store_state, cdp_navigation_state) keep working throughout, and device_screenshot serves pixels via its OS-level simctl fallback. The guard clears within ~5 s of the foreign run ending; the first tap within ~10 s after rn-dev-agent’s own flows is exempt (WDA teardown grace). Disable with RN_IOS_FOREIGN_GUARD=0 (RN_IOS_FOREIGN_WARN=0 is a deprecated alias — it disables the refusal too, not just this warning).

Division of labor: rn-dev-agent’s maestro_run is the canonical Maestro surface — it participates in the device arbiter, parks the L2 interaction runner for the flow, marks CDP stale afterwards, and auto-repairs saved actions. Use the standalone maestro-mcp for ad-hoc exploration; when both target one simulator, rn-dev-agent now steps back cleanly instead of fighting.

The detection is UDID-scoped (it ignores maestro flows on other simulators and the idle maestro-mcp server). Opt out with RN_IOS_FOREIGN_GUARD=0 (disables the refusal too). Note: immediately after your own maestro_run, you may briefly see the warning while maestro’s driver is still exiting — that’s expected, and the refusal guard explicitly exempts that window.

A maestro_run / maestro_test_all flow (rn-dev-agent’s own L3) takes the device exclusively for its duration — device_* taps and CDP writes issued mid-flow refuse fast with BUSY_FLOW_ACTIVE. (CDP reads — cdp_component_tree, cdp_store_state, etc. — are L1 and coexist; see above.)

Exception — device_screenshot (#210): a screenshot during a flow does not refuse. It transparently captures via xcrun simctl io screenshot (or adb), which is OS-level and can’t conflict with maestro’s WDA, so you always get pixels (via: 'simctl' in the result). This is the only device_* verb that interleaves with a flow, because it has a safe non-XCUITest path.

Why there’s no shared “WDA session” to ride (#210)

Section titled “Why there’s no shared “WDA session” to ride (#210)”

A natural question: since maestro drives the device fine via WebDriverAgent, can device_* “ride” that WDA session instead of rn-fast-runner? No — maestro-runner spawns WDA per flow and tears it down when the flow exits, so there’s no persistent session to attach to, and a WDA client would add a second XCUITest backend (the opposite of unifying). The coherent model is: rn-fast-runner is the device_* backend (it auto-spawns when prebuilt — cdp_status.deviceSession shows its alive/stale/dead state), maestro/WDA is the flow engine, and they’re serialized. Mid-flow, use simctl for pixels and CDP reads for state/tree.

Using rn-dev-agent with React Native DevTools

Section titled “Using rn-dev-agent with React Native DevTools”

The same coexistence question exists for the visual React Native DevTools window, which competes with the bridge for the single debugger seat. That has its own opt-out (RN_CDP_AUTOCONNECT=0 / .rn-agent/config.json) and is covered in a dedicated guide: Using rn-dev-agent with React Native DevTools.