You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(protocol): version the runner /command wire protocol + move runner state out of /tmp (#383) (#398)
* docs(plan): TDD implementation plan for #383 runner wire-protocol versioning + /tmp state relocation
Amendments applied from the multi-LLM plan review (Codex + Claude coordinator;
Gemini failed on interactive OAuth):
- BLOCKER: legacy /tmp state is now adopted once (lenient, protocolVersion 0)
so a live pre-upgrade runner is discovered -> classified legacy -> reaped ->
relaunched; /tmp files deleted only after the relaunch persists new state.
- BLOCKER: stopFastRunner/stopAndroidRunner + device-interact
isFastRunnerAvailable fast-paths are adoption-aware (deviceId-scoped), so a
post-respawn close/restart/park never leaks a live runner.
- BLOCKER: Android mismatch reap reuses releaseAndroidInteractionSlot() (#237,
force-stops BOTH owned packages) via dynamic import.
- BLOCKER: fixed self-contradictory sanitize-test assertion (no '/' survives).
- IMPORTANT: no android-default state key - persist only under a resolved
serial; acceptance criterion holds unconditionally.
- IMPORTANT: added missed fixture test/unit/runners/rn-android-runner-client
.test.js (catch-all fetch mock must branch /health vs /command).
- MINOR: pendingUpgradeNote cleared on the mismatch-reject path.
* feat(protocol): RUNNER_PROTOCOL_VERSION constants + tri-file sync test (#383)
* refactor(state): extract hardened secure-state-file util from session-file IO (#383)
* feat(ios-runner): per-UDID hardened state file replaces /tmp singleton (#383)
Relocate iOS rn-fast-runner state from the shared /tmp/rn-fast-runner-state.json
singleton to a per-UDID hardened file under <stateDir>/runner-state/ios-<udid>.json
(schema v1, protocolVersion stamp). Replace the import-time /tmp load with lazy
per-device adoption (adoptPersistedFastRunnerState) so a respawned worker
rediscovers a live runner. Adoption-aware teardown: stopFastRunner(deviceId?)
adopts before reaping; deviceId threaded through every stop call site. Legacy
/tmp state is read once (protocolVersion 0) so a live pre-upgrade runner is
adopted not orphaned, and deleted only after a fresh relaunch persists the new
per-device file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ios-runner): pass deviceId at the two remaining stopFastRunner sites in device-session.ts (#383)
stopFastRunner(deviceId?) now adopts persisted per-device state before
reaping; a bare call after a bridge-worker respawn no-ops and leaks the
runner. Both remaining call sites had a deviceId in scope (session and
a function parameter, respectively) but weren't passing it.
* chore(lint): fix oxlint/oxfmt drift in gh-383 files (#383)
Remove an unused readFileSync import in gh-383-secure-state-file.test.js
(oxlint no-unused-vars) and re-run oxfmt on secure-state-file.ts and
gh-383-protocol-sync.test.js to clear formatter drift from earlier
commits on this branch. dist rebuilt for the util source formatting
change.
* feat(android-runner): per-serial hardened state file + no-/tmp static invariant (#383)
* feat(runners): /health protocolVersion+runnerVersion+capabilities, v stamp on every response (#383)
* feat(ios-gate): protocol/version-aware liveness -> stale -> reap; RUNNER_PROTOCOL_MISMATCH after failed reinstall (#383)
* feat(android-gate): protocol verify on reuse + post-start; forced reinstall on mismatch (#383)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surface): deviceSession.runnerProtocol in cdp_status; doctor + docs + changeset (#383)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(surface): thread RUNNER_PROTOCOL_MISMATCH + upgrade note through device-session open paths (#383)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(surface): discard stale Android upgrade note in device-session open catch; type runnerProtocol on StatusResult (#383)
* fix(park): thread deviceId through maestro park + android close paths; tighten open-catch wiring test (#383)
Fix 1 (critical): runFlowParked's iOS branch called stopFastRunner()
bare, so after a bridge-worker respawn wiped in-memory state, parking
the L2 runner ahead of a Maestro flow silently no-op'd instead of
adopting the persisted per-device state and killing it — a
flow-exclusivity regression. Pass opts.deviceId through and widen the
FlowParkOpts.stopFastRunner type to accept it.
Fix 2 (important): the two Android close paths (device-session.ts's
runner-leak-recovery closeSession, and device-session-close.ts's two
teardown branches) called stopAndroidRunner() with no deviceId,
missing the same adoption-aware teardown the iOS twins already got.
Widened CloseDeviceSessionDeps.stopAndroidRunner to
(deviceId?: string) => Promise<void> and threaded the in-scope
deviceId through all three call sites.
Fix 3 (test): the gh-383 open-catch wiring test sliced from
'// Ensure runner + launch.', which includes the try body's own
consumePendingAndroidUpgradeNote() call on the success path — so
indexOf() matched that call instead of the catch-path discard,
making the assertion pass even with the catch-path discard removed.
Re-anchored the slice to start at 'catch (err)'. Verified locally
(then reverted) that removing the catch-path discard now fails the
test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ios-runner): pass RN_PLUGIN_VERSION via TEST_RUNNER_ prefix so xcodebuild forwards it (#383)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
feat(protocol): version the native runner /command wire protocol + move runner state out of /tmp (#383). Both runners' `GET /health` now reports `{protocolVersion, runnerVersion, capabilities}` and every response carries a `"v"` stamp; the bridge classifies a reachable runner with a missing/older/newer protocol or a skewed `runnerVersion` as stale and transparently reaps + reinstalls it (the first device tool call after upgrading from a pre-protocol plugin pays one runner restart — `meta.note: "runner upgraded (protocol/version mismatch)"`). Only a mismatch that survives reinstall surfaces the new typed error `RUNNER_PROTOCOL_MISMATCH` with exact rebuild commands. Runner state files move from fixed shared `/tmp` paths to per-device hardened files (0600, symlink-refusing, atomic) under the app-support state dir (`runner-state/ios-<udid>.json`, `android-<serial>.json`; Android persists only under a resolved serial) via a shared `util/secure-state-file.ts` also adopted by the session file; a live pre-upgrade runner pointed at by the legacy `/tmp` state is adopted once, reaped, and relaunched before the `/tmp` files are deleted, and a grep-enforced test keeps `/tmp` out of the runner clients. `cdp_status` → `deviceSession.runnerProtocol` surfaces the handshake.
Copy file name to clipboardExpand all lines: commands/doctor.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ argument-hint:
6
6
7
7
Run the environment-diagnostic checklist from the `rn-setup` skill. Walk all 15 prerequisite checks (Node.js version, CDP bridge dependencies, **rn-fast-runner build (iOS)**, **rn-android-runner build/install (Android)**, maestro-runner, iOS simulator, Android emulator, Metro dev server, CDP connection, **injected `__RN_AGENT` helpers**, ffmpeg, physical-device prerequisites, **plugin version freshness**, **Vercel rules sync freshness**, **CDP auto-reconnect mode**) and surface install commands for any missing dependencies.
8
8
9
-
iOS device automation is owned by the in-tree `rn-fast-runner` XCTest project (D1219, PR #164); Android device automation is owned by the in-tree `rn-android-runner` (UiAutomator instrumentation). These in-tree runners are the sole device backend — there is no external CLI to install. Mark `rn-android-runner` as N/A on iOS-only setups and `rn-fast-runner` as N/A on Android-only / non-macOS setups.
9
+
iOS device automation is owned by the in-tree `rn-fast-runner` XCTest project (D1219, PR #164); Android device automation is owned by the in-tree `rn-android-runner` (UiAutomator instrumentation). These in-tree runners are the sole device backend — there is no external CLI to install. Mark `rn-android-runner` as N/A on iOS-only setups and `rn-fast-runner` as N/A on Android-only / non-macOS setups. If a device tool fails with RUNNER_PROTOCOL_MISMATCH, the installed/prebuilt runner artifact predates the plugin's wire protocol: on iOS delete scripts/rn-fast-runner/build/DerivedData and re-open the device session (or re-run xcodebuild build-for-testing); on Android re-run ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest and adb install -r both APKs.
10
10
11
11
**This command is read-only.** It diagnoses the current environment and recommends fixes. It does NOT modify any files in the user's project, inject documentation, or instrument source code.
`The prebuilt XCUITest artifact is stale — rebuild it: delete scripts/rn-fast-runner/build/DerivedData `+
531
+
`and re-open the device session (cold build), or run xcodebuild build-for-testing (see plugin Prerequisites).`,
532
+
};
533
+
}
538
534
return{
539
535
ok: false,
540
536
message: 'rn-fast-runner did not become ready after auto-spawn. Retry, or run `device_snapshot action=open appId=<your.app.id> platform=ios` to surface the build error.',
@@ -584,6 +580,27 @@ export function _setRunAgentDeviceForTest(fn) {
584
580
}
585
581
_runAgentDeviceOverrideForTest=fn;
586
582
}
583
+
// GH #383: tool results are MCP envelopes (JSON text in content[0]) — attach
0 commit comments