Skip to content

Project

Troubleshooting

Find your symptom in the quick reference, or jump to the error-code catalog if a tool returned a code in SCREAMING_SNAKE_CASE.

SymptomGo to
CDP won’t connect / 1006Connection
DevTools keeps disconnectingConnection
cdp_store_state empty or errorsStore state
Plugin/skills not detectedPlugin install
Install/update fails (ENOTEMPTY, ENAMETOOLONG)Plugin install
Taps/flows fail on the deviceDevice & flows
A tool returned RN_…/BUSY_…/RUNNER_…Error codes
Action replay failsActions & setup

Start Metro first: npx expo start or npx react-native start. The plugin auto-detects ports 8081, 8082, 19000, and 19006.

Open the app on the simulator and ensure Hermes is enabled. Check your app.json or metro.config.js for Hermes configuration.

Close React Native DevTools, Flipper, or Chrome DevTools — only one debugger can connect at a time. The 1006 close code means session conflict.

DevTools shows “Disconnected due to opening a second DevTools window”

Section titled “DevTools shows “Disconnected due to opening a second DevTools window””

The bridge auto-reconnects by default and evicts the visual React Native DevTools from the single debugger seat. Set RN_CDP_AUTOCONNECT=0 (or .rn-agent/config.json{ "cdp": { "autoConnect": false } }) to let DevTools hold the seat — the bridge then connects only when a CDP tool runs. Full guide: React Native DevTools coexistence.

This is normal. cdp_reload automatically reconnects within 15 seconds. If it fails, call cdp_status to re-establish the connection.

Fixed since #264: the bridge entry point is a stdio supervisor that holds no network sockets, so port-based kills (lsof -ti tcp:8081 | xargs kill -9) only take the worker process — the supervisor respawns it automatically and the session keeps its tools (cdp_statusbridge.workerRestarts). If tools error with “worker is crash-looping”, check the bridge log (LOG_LEVEL=info writes one) and restart the Claude Code session. RN_BRIDGE_SUPERVISOR=0 opts back into the legacy single-process bridge.

Run /rn-dev-agent:setup — it scaffolds the dev-bridge and proposes a getBridge()?.registerStores({ ... }) line in your app entry. The bridge call replaces the older globalThis.__ZUSTAND_STORES__ pattern.

Manual fallback if you want to wire it yourself:

import { getBridge } from './.rn-agent/dev-bridge';
getBridge()?.registerStores({
auth: useAuthStore,
cart: useCartStore,
});

Redux is auto-detected — no setup needed.

cdp_store_state returns empty for React Query

Section titled “cdp_store_state returns empty for React Query”

Use storeType: 'react-query' explicitly. React Query state is read from the QueryClient, which requires the provider to be mounted.

In Claude Code, run /plugin install rn-dev-agent@rn-dev-agent then /reload-plugins. For local development, Claude Code uses claude --plugin-dir /path/to/rn-dev-agent; Codex uses the plugin package path /path/to/rn-dev-agent/packages/codex-plugin.

Update Claude Code to v1.0.33+: npm update -g @anthropic-ai/claude-code

Run /reload-plugins or clear cache: rm -rf ~/.claude/plugins/cache

Clear the stale cache first:

Terminal window
rm -rf ~/.claude/plugins/cache/rn-dev-agent

Then retry the update.

Clear stale cache:

Terminal window
rm -rf ~/.claude/plugins/cache/rn-dev-agent ~/.claude/plugins/data/rn-dev-agent-*

Then re-add marketplace and install.

Add to your shell profile: export PATH="$HOME/.maestro-runner/bin:$PATH"

The crash is native, not JavaScript. Use adb logcat -b crash (Android) or check Xcode console (iOS) for native crash logs.

Ensure the emulator is booted and adb devices shows it. The plugin auto-selects the first available device. Set ANDROID_SERIAL to target a specific emulator.

Use maestro-runner instead of classic Maestro — it uses HTTP to UIAutomator2 instead of gRPC. The plugin enforces this by default.

Codes returned by device_*/flow tools, what they mean, and the fix.

RN_FAST_RUNNER_DOWN / RN_ANDROID_RUNNER_DOWN

Section titled “RN_FAST_RUNNER_DOWN / RN_ANDROID_RUNNER_DOWN”

The in-tree device runner couldn’t start. Check that the simulator/emulator is booted and (Android) the SDK is available. iOS self-builds on first use — a cold build takes several minutes; if it times out, pre-build once with xcodebuild build-for-testing (see Getting Started) and re-open the device session.

A foreign Maestro/XCUITest session (e.g. a standalone maestro-mcp) is driving the same simulator. Wait for it to finish (the guard clears within ~5 s), use CDP reads and device_screenshot meanwhile, or disable the guard with RN_IOS_FOREIGN_GUARD=0.

Your own Maestro flow holds the device (L3 is exclusive). Reads (cdp_component_tree, cdp_store_state) still work mid-flow; taps are refused on purpose until the flow ends.

The bridge and the native runner disagree on the wire protocol and the automatic reinstall didn’t clear it. iOS: delete scripts/rn-fast-runner/build/DerivedData and re-open the device session (cold rebuild). Android: rebuild the runner APKs (./gradlew :app:assembleDebug :app:assembleDebugAndroidTest).

RUNNER_COMMANDS_STALE / UNSUPPORTED_COMMAND

Section titled “RUNNER_COMMANDS_STALE / UNSUPPORTED_COMMAND”

The runner build predates a newer command verb. Re-open the device session (device_snapshot action=open) — the stale artifact is rebuilt automatically (one multi-minute build on iOS). If a cold build still reports missing commands, update the plugin checkout.

A tap target sits under the software keyboard and the safe dismiss control isn’t available (iPhone standard QWERTY). Dismiss the keyboard first (e.g. cdp_evaluateKeyboard.dismiss()) or fill via device_fill, which is JS-first and needs no keyboard. Opt the guard off with RN_KEYBOARD_GUARD=0.

Another project’s session holds this simulator (UDID-scoped ownership lock). Close the other session, or wait — the lock self-heals when its holder exits (PID-liveness + 90 s heartbeat staleness).

An @ref from an old snapshot no longer resolves uniquely. Re-run device_snapshot and use a fresh ref; unique testID/label matches are re-bound automatically, so persistent STALE_REF with a candidates list means the match is ambiguous — disambiguate by testID.

Action replay fails with SELECTOR_NOT_FOUND

Section titled “Action replay fails with SELECTOR_NOT_FOUND”

The plugin attempts to self-repair small UI drift: it looks at the live UI, finds the renamed testID via fuzzy match, patches the action YAML, and retries.

Self-repair refuses if the file has been hand-edited (mtime check), or after 3 repairs in 24h. If repair is refused or fails, the plugin asks before forcing. If the underlying flow has changed structurally (extra step, removed screen), re-record with /rn-dev-agent:test-feature.

See the Actions guide for full details.

Re-run /rn-dev-agent:setup. It’s idempotent — already-injected blocks are detected and skipped, so only the missing pieces get applied.

That directory holds runtime state (run history, repair history) — not config. Check that .rn-agent/.gitignore exists and contains state/, recordings/, snapshots/, diag/, index.json. The /setup scaffold ships this file; if it’s missing, regenerate via /setup (idempotent — only the missing pieces are added).