Skip to content

Commit 6e274a1

Browse files
abretonc7sclaude
andauthored
feat(agentic): improve preflight visibility and CDP discovery resilience (#28785)
## **Description** Improve the mobile agentic preflight scripts so they are more reliable on slower environments and easier for humans to follow during long-running stages. ### Why - CDP discovery could fail too early while polling Metro `/json/list`. - Long-running preflight steps were noisy and hard to debug from the main console output alone. ### What changed - `scripts/perps/agentic/lib/target-discovery.js` - retry `/json/list` discovery before failing - support `CDP_TIMEOUT` - support `CDP_DISCOVERY_RETRIES` - `scripts/perps/agentic/preflight.sh` - add per-stage logs under `PREP_LOG_DIR` (default: `.agent/preflight-logs`) - log deps, pod install, CDP, and wallet setup separately - print each stage log path when the stage starts - keep stdout concise while preserving detailed logs - `scripts/perps/agentic/app-state.sh` - `scripts/perps/agentic/setup-wallet.sh` - inherit the same CDP retry knobs ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: N/A ## **Manual testing steps** ```gherkin Feature: Mobile preflight reliability and observability Scenario: Clean preflight succeeds in a fresh environment Given a MetaMask Mobile worktree prepared for iOS development When I run `bash scripts/perps/agentic/preflight.sh --platform ios --clean --wallet-setup` Then the app build/install succeeds And Metro is reachable on the configured port And CDP connects successfully And wallet setup completes successfully Scenario: Warm preflight succeeds in a reused environment Given the same worktree is already prepared When I run `bash scripts/perps/agentic/preflight.sh --platform ios --wallet-setup` Then CDP connects successfully And wallet setup completes successfully Scenario: Cross-environment validation succeeds Given the same script changes are applied in more than one environment When I run the clean and warm preflight flows Then the preflight output remains readable And the stage logs are written consistently And CDP discovery remains stable ``` ## **Screenshots/Recordings** ### **Before** - N/A — script/logging change ### **After** - N/A — script/logging change ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: changes are confined to local developer/agentic shell and Node scripts, mainly adding logging and retry/timeout knobs. Main risk is regressions in preflight automation due to altered timeouts, paths, or command output handling. > > **Overview** > Improves the `agentic` preflight flow by adding **per-stage log files** (deps install, CocoaPods, builds, CDP polling, wallet setup) with a configurable `PREP_LOG_DIR`, keeping console output concise while surfacing where to look for full details. > > Makes CDP target discovery more resilient by adding configurable **fetch timeouts and retries** (`CDP_TIMEOUT`, `CDP_DISCOVERY_RETRIES`) for Metro `/json/list` polling, and propagates these env knobs through `app-state.sh` and `setup-wallet.sh`. Preflight’s CDP wait loop now captures each status attempt to a log and prints more actionable diagnostics on timeout (including probing 8081 vs the configured port). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4684576. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 38bb5d0 commit 6e274a1

4 files changed

Lines changed: 81 additions & 15 deletions

File tree

scripts/perps/agentic/app-state.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ set -euo pipefail
2424

2525
cd "$(dirname "$0")/../../.."
2626

27+
export CDP_TIMEOUT="${CDP_TIMEOUT:-30000}"
28+
export CDP_DISCOVERY_RETRIES="${CDP_DISCOVERY_RETRIES:-3}"
29+
2730
COMMAND="${1:-route}"
2831
shift || true
2932

scripts/perps/agentic/lib/target-discovery.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ const http = require('node:http');
44
const { loadSimulatorName, loadAndroidDevice } = require('./config');
55
const { createWSClient } = require('./ws-client');
66

7+
const FETCH_TIMEOUT_MS = Number.parseInt(process.env.CDP_TIMEOUT || '30000', 10);
8+
const FETCH_RETRIES = Number.parseInt(process.env.CDP_DISCOVERY_RETRIES || '3', 10);
9+
710
/** Fetch JSON from a URL (http only, no external deps) */
8-
function fetchJSON(url) {
11+
function fetchJSONOnce(url) {
912
return new Promise((resolve, reject) => {
1013
const req = http.get(url, (res) => {
1114
let data = '';
@@ -19,13 +22,27 @@ function fetchJSON(url) {
1922
});
2023
});
2124
req.on('error', reject);
22-
req.setTimeout(5000, () => {
25+
req.setTimeout(FETCH_TIMEOUT_MS, () => {
2326
req.destroy();
24-
reject(new Error(`Timeout fetching ${url}`));
27+
reject(new Error(`Timeout fetching ${url} after ${FETCH_TIMEOUT_MS}ms`));
2528
});
2629
});
2730
}
2831

32+
async function fetchJSON(url) {
33+
let lastError;
34+
for (let attempt = 1; attempt <= FETCH_RETRIES; attempt++) {
35+
try {
36+
return await fetchJSONOnce(url);
37+
} catch (error) {
38+
lastError = error;
39+
if (attempt === FETCH_RETRIES) break;
40+
await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
41+
}
42+
}
43+
throw lastError;
44+
}
45+
2946
/**
3047
* Quick probe: connect to a CDP target, evaluate `__DEV__`, disconnect.
3148
* Returns true if __AGENTIC__ is installed, false otherwise.

scripts/perps/agentic/preflight.sh

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
set -euo pipefail
3838

3939
cd "$(dirname "$0")/../../.."
40+
ROOT_DIR="$(pwd)"
4041
# Source .js.env but only for vars not already set, so caller env takes precedence.
4142
if [ -f .js.env ]; then
4243
while IFS= read -r _line || [ -n "$_line" ]; do
@@ -49,10 +50,22 @@ if [ -f .js.env ]; then
4950
unset _line _key
5051
fi
5152

53+
resolve_path() {
54+
case "$1" in
55+
/*) printf '%s\n' "$1" ;;
56+
*) printf '%s/%s\n' "$ROOT_DIR" "$1" ;;
57+
esac
58+
}
59+
5260
PORT="${WATCHER_PORT:-8081}"
5361
SCRIPTS="scripts/perps/agentic"
54-
LOGFILE=".agent/metro.log"
55-
CDP_TIMEOUT=90
62+
LOGFILE="$(resolve_path '.agent/metro.log')"
63+
LOG_DIR="$(resolve_path "${PREP_LOG_DIR:-.agent/preflight-logs}")"
64+
DEPS_LOG="${LOG_DIR}/deps.log"
65+
POD_INSTALL_LOG="${LOG_DIR}/pod-install.log"
66+
CDP_LOG="${LOG_DIR}/cdp.log"
67+
WALLET_LOG="${LOG_DIR}/wallet-setup.log"
68+
CDP_WAIT_TIMEOUT=90
5669
CDP_RETRY=0
5770

5871
# Flags
@@ -106,6 +119,7 @@ GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; BO
106119
ok() { echo -e " ${GREEN}${NC} $*"; }
107120
warn() { echo -e " ${YELLOW}${NC} $*"; }
108121
fail() { echo -e " ${RED}${NC} $*"; exit 1; }
122+
stage_log() { echo -e " ${DIM}Log: $1${NC}"; }
109123

110124
# Kill a process and all its descendants.
111125
# We need this because `$!` after `eval "$EXPO_CMD" &` points at the yarn
@@ -171,6 +185,8 @@ if $DO_WALLET_SETUP; then
171185
ok "Fixture validated: $WALLET_FIXTURE (password + ${FIX_ACCT_COUNT} accounts)"
172186
fi
173187

188+
mkdir -p "$LOG_DIR"
189+
174190
# Timing
175191
PREFLIGHT_START=$(python3 -c "import time; print(int(time.time()))")
176192
STEP_START=$PREFLIGHT_START
@@ -270,7 +286,14 @@ if $DO_CLEAN; then
270286
echo " Cleaning Android build artifacts..."
271287
rm -rf android/app/build
272288
fi
273-
yarn setup 2>&1 | tail -3
289+
stage_log "$DEPS_LOG"
290+
printf '$ yarn setup\n' > "$DEPS_LOG"
291+
if ! yarn setup >>"$DEPS_LOG" 2>&1; then
292+
echo ""
293+
echo -e " ${RED}Dependencies failed — see $DEPS_LOG${NC}"
294+
tail -20 "$DEPS_LOG" | sed 's/^/ /'
295+
fail "yarn setup failed"
296+
fi
274297
ok "yarn setup complete"
275298
fi
276299
@@ -313,7 +336,13 @@ if [ "$PLAT" = "ios" ]; then
313336
echo ""
314337
315338
echo " Running pod install via bundler..."
316-
(cd ios && bundle exec pod install --repo-update --ansi 2>&1 | tail -3) || warn "pod install had issues"
339+
stage_log "$POD_INSTALL_LOG"
340+
printf '$ (cd ios && bundle exec pod install --repo-update --ansi)\n' > "$POD_INSTALL_LOG"
341+
if (cd ios && bundle exec pod install --repo-update --ansi >>"$POD_INSTALL_LOG" 2>&1); then
342+
ok "pod install complete"
343+
else
344+
warn "pod install had issues — see $POD_INSTALL_LOG"
345+
fi
317346
318347
# Must pass --port (never --no-bundler): @expo/cli rejects that combo
319348
# (resolveBundlerProps.js), and without --port expo's headless bundler silently
@@ -330,9 +359,10 @@ if [ "$PLAT" = "ios" ]; then
330359
# subprocess tree. We must walk the tree (yarn → corepack → yarn.cjs → node
331360
# expo/cli) because killing only $! leaves the deep child alive and orphaned,
332361
# still holding $PORT — which would defeat start-metro.sh and break CDP.
333-
BUILD_LOG=".agent/ios-expo-build.log"
362+
BUILD_LOG="$(resolve_path '.agent/ios-expo-build.log')"
334363
mkdir -p "$(dirname "$BUILD_LOG")"
335364
: > "$BUILD_LOG"
365+
stage_log "$BUILD_LOG"
336366
BUILD_START=$(date +%s)
337367
338368
set +e
@@ -502,7 +532,8 @@ else
502532
echo -n "$GOOGLE_SERVICES_B64_ANDROID" | base64 -d > ./android/app/google-services.json
503533
fi
504534
505-
BUILD_LOG=".agent/android-build.log"
535+
BUILD_LOG="$(resolve_path '.agent/android-build.log')"
536+
stage_log "$BUILD_LOG"
506537
set +e
507538
(cd android && SENTRY_DISABLE_AUTO_UPLOAD=true ./gradlew app:assembleProdDebug -PreactNativeArchitectures=arm64-v8a) 2>&1 | tee "$BUILD_LOG" | while IFS= read -r line; do
508539
case "$line" in
@@ -538,6 +569,7 @@ fi
538569
539570
# ── Step: Metro ─────────────────────────────────────────────────────
540571
step "Starting Metro" "Bundler on port $PORT → logs at $LOGFILE"
572+
stage_log "$LOGFILE"
541573
# start-metro.sh detects running Metro and skips start. With --launch it
542574
# opens the app via expo deeplink for the target platform regardless.
543575
bash "$SCRIPTS/start-metro.sh" --platform "$PLAT" $($DO_LAUNCH && echo "--launch" || echo "")
@@ -549,8 +581,12 @@ ok "Metro running on port $PORT"
549581
550582
# ── Step: CDP ───────────────────────────────────────────────────────
551583
step "Connecting CDP" "Waiting for app to expose debug target"
552-
while [ $CDP_RETRY -lt $CDP_TIMEOUT ]; do
553-
if node "$SCRIPTS/cdp-bridge.js" status 2>/dev/null | grep -q '"route"' 2>/dev/null; then
584+
stage_log "$CDP_LOG"
585+
printf '$ node %s/cdp-bridge.js status\n' "$SCRIPTS" > "$CDP_LOG"
586+
while [ $CDP_RETRY -lt $CDP_WAIT_TIMEOUT ]; do
587+
CDP_STATUS_OUTPUT=$(node "$SCRIPTS/cdp-bridge.js" status 2>&1 || true)
588+
printf '[attempt %s]\n%s\n' "$((CDP_RETRY + 1))" "$CDP_STATUS_OUTPUT" >>"$CDP_LOG"
589+
if echo "$CDP_STATUS_OUTPUT" | grep -q '"route"' 2>/dev/null; then
554590
ok "CDP connected"
555591
break
556592
fi
@@ -559,7 +595,7 @@ while [ $CDP_RETRY -lt $CDP_TIMEOUT ]; do
559595
[ $CDP_RETRY -eq 5 ] && echo -e " ${DIM}Still waiting... app may still be loading JS bundle${NC}"
560596
[ $CDP_RETRY -eq 15 ] && echo -e " ${DIM}Taking longer than usual — check device${NC}"
561597
done
562-
if [ $CDP_RETRY -ge $CDP_TIMEOUT ]; then
598+
if [ $CDP_RETRY -ge $CDP_WAIT_TIMEOUT ]; then
563599
# Diagnostic: probe candidate ports before failing. The symptom we hit most
564600
# often is the app registering its Hermes inspector on 8081 instead of our
565601
# $PORT when a stale expo dev server lingers — surface that explicitly so we
@@ -595,11 +631,12 @@ for p in json.loads(sys.stdin.read() or "[]"):
595631
echo -e " ${DIM}A stale 'expo run:ios' without --port is likely holding 8081.${NC}"
596632
echo -e " ${DIM}Run: pgrep -fl 'expo run:ios' to confirm, then kill it and retry.${NC}"
597633
fi
598-
fail "CDP did not become available after ${CDP_TIMEOUT}s"
634+
fail "CDP did not become available after ${CDP_WAIT_TIMEOUT}s — see $CDP_LOG"
599635
fi
600636
601637
# Verify CDP is connected to the right platform (status may return object or array)
602-
CDP_STATUS=$(node "$SCRIPTS/cdp-bridge.js" status 2>/dev/null || true)
638+
CDP_STATUS=$(node "$SCRIPTS/cdp-bridge.js" status 2>>"$CDP_LOG" || true)
639+
printf '[final-status]\n%s\n' "$CDP_STATUS" >>"$CDP_LOG"
603640
CDP_HAS_PLAT=$(echo "$CDP_STATUS" | jq -r 'if type == "array" then [.[].platform] else [.platform] end | map(select(. == "'"$PLAT"'")) | length' 2>/dev/null || echo 0)
604641
if [ "$CDP_HAS_PLAT" = "0" ]; then
605642
warn "CDP did not find $PLAT app — it may still be loading"
@@ -614,7 +651,14 @@ if $DO_WALLET_SETUP; then
614651
FIXTURE_FLAG=""
615652
[ "$WALLET_FIXTURE" != ".agent/wallet-fixture.json" ] && FIXTURE_FLAG="--fixture $WALLET_FIXTURE"
616653
if [ -f "$WALLET_FIXTURE" ] || [ -n "$FIXTURE_FLAG" ]; then
617-
bash "$SCRIPTS/setup-wallet.sh" $FIXTURE_FLAG && ok "Wallet configured" || warn "Wallet setup failed — check fixture file"
654+
stage_log "$WALLET_LOG"
655+
printf '$ bash %s/setup-wallet.sh %s\n' "$SCRIPTS" "$FIXTURE_FLAG" > "$WALLET_LOG"
656+
if bash "$SCRIPTS/setup-wallet.sh" $FIXTURE_FLAG >>"$WALLET_LOG" 2>&1; then
657+
tail -12 "$WALLET_LOG" | sed 's/^/ /'
658+
ok "Wallet configured"
659+
else
660+
warn "Wallet setup failed — see $WALLET_LOG"
661+
fi
618662
else
619663
warn "No fixture at $WALLET_FIXTURE"
620664
echo -e " ${DIM}cp scripts/perps/agentic/wallet-fixture.example.json .agent/wallet-fixture.json${NC}"
@@ -638,6 +682,7 @@ echo -e " Platform ${DIM}$PLAT${NC}"
638682
echo -e " Metro ${DIM}http://localhost:$PORT/status${NC}"
639683
echo -e " Logs ${DIM}tail -f $LOGFILE${NC}"
640684
echo -e " CDP ${DIM}node $SCRIPTS/cdp-bridge.js status${NC}"
685+
echo -e " Stage logs ${DIM}$LOG_DIR${NC}"
641686
echo ""
642687
echo -e "${DIM}Timing:${NC}"
643688
echo -e "$STEP_TIMES"

scripts/perps/agentic/setup-wallet.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ fi
3535
PORT="${WATCHER_PORT:-8081}"
3636
SCRIPTS="$(dirname "$0")"
3737
export CDP_TIMEOUT="${CDP_TIMEOUT:-30000}"
38+
export CDP_DISCOVERY_RETRIES="${CDP_DISCOVERY_RETRIES:-3}"
3839
CDP="node $SCRIPTS/cdp-bridge.js"
3940
FIXTURE_PATH=""
4041

0 commit comments

Comments
 (0)