eas-cli too old — --name was added after simulator:start itself
Run via npx --yes eas-cli@latest …, or upgrade eas-cli. If you can’t upgrade, retry once without--name; the session starts unnamed.
An Expo user account is required / whoami shows logged-out
No browser login on a cloud/CI/headless box, or EXPO_TOKEN unset/invalid
Set EXPO_TOKEN (expo.dev → Account → Access Tokens) in the env; verify npx --yes eas-cli@latest whoami. (Interactive machines can eas login.)
simulator:start/build: no linked project / missing projectId
A fresh create-expo-app isn’t linked to EAS
npx --yes eas-cli@latest init to create/link it (writes extra.eas.projectId).
prebuild/eas build prompts for or fails on a missing iOS bundle identifier
A fresh app often has no ios.bundleIdentifier
Set it in app config (e.g. dev.<owner>.<slug>); confirm via npx expo config --json (may live in app.config.js).
--max-duration-minutes rejected
The flag is paid-plan only
Drop --max-duration-minutes to use the default.
simulator:start fails with not enabled for this account / not-allowlisted
EAS Simulator is limited-access and isn’t enabled for this account
Don’t retry. Confirm with simulator:availability, then hand off gracefully — tell the user and fall back to a local sim / EAS Build (see SKILL.md Check availability first).
start keeps “Waiting for … session to be ready” but it never returns
start‘s readiness poll can miss a session that’s actually live
Don’t rely on it — poll npx --yes eas-cli@latest simulator:get --id <id> --json for status: IN_PROGRESS + a populated remoteConfig.
ERR_NGROK_3200 / endpoint offline; Remote daemon is unavailable
The session’s tunnel/daemon dropped — left idle and timed out, or the VM was torn down
A drop invalidates the whole session (installed app, @e refs, Metro). Don’t retry the failed verb — start a fresh session, reset the dotenv, and re-run install→open→drive from the top, acting immediately.
Two sessions running / orphaned session / surprise double billing
A second start (e.g. to “retry” a slow boot) creates a second billed session and overwrites the dotenv id, orphaning the first
Never start again to retry — poll the existing session instead. Find orphans with simulator:list --status IN_PROGRESS and stop each with simulator:stop --id <id>.
A device verb hangs (no return for a minute+)
Slow daemon; press/screenshot can block ~90s
Bound it with agent-device’s own --timeout <ms> (e.g. --timeout 120000) — not a shell timeout wrapper (macOS has no timeout binary, so timeout 120 … fails with command not found and skips the verb). On timeout snapshot -i to see if the action landed before retrying (taps can double-fire). Don’t blind-retry.
install requires an active session or an explicit device selector
install can’t infer the device
Pass --platform ios (or open something first to establish a session).
Unknown command: tap
The tap verb is press
Use press <ref|selector> (e.g. press @e2 or press 'label="Open"').
SESSION_NOT_FOUND: No active session. Run open first.
A verb (e.g. screenshot) ran before any app/session was opened
open <app|url> first (or pass --platform ios).
simulator:exec / build / simulator:stop: “Run this command inside a project directory.”
Run from the wrong cwd
Run from the Expo project directory (where app.json/eas.json live).
New session’s id shows as the previous one; “Overwriting previous simulator session (id: …)”
The stale .env.eas-simulator had an old EAS_SIMULATOR_SESSION_ID; the warning line masks the new id
Reset the dotenv before start: printf '# managed by eas-cli\n' > .env.eas-simulator.
No .env.eas-simulator written after start
--json suppresses the dotenv
Run startwithout--json for the exec flow; with --json you must read remoteConfig from stdout and set the env yourself.
pod install fails: Unicode Normalization not appropriate for ASCII-8BIT
Ruby 4 + CocoaPods with a non-UTF-8 locale
Re-run with LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pod install.
(Mode C) Deep-link open lands on the dev-client launcher, not the app
Opening the deep link triggers a system “Open in ‘‘?” dialog; and the launcher only auto-discovers Metro on the LAN
press 'label="Open"' to dismiss the dialog, then “Enter URL manually” → fill the https://<host>.on.expo.app manifest URL → “Connect”.
(Mode C) App shows expo-router “Unmatched Route”
The connect URL was parsed as a route path
press 'label="Go back"' (or navigate to /).
(Mode C) Dev client shows a ? placeholder / blank after connect
Bundle not fetched yet
press 'label="Reload"' and wait ~40-60s for the first build+transfer over the tunnel.
(Mode C) expo start fails: “port 8081 already in use”
Something already owns that port (often another Metro)
Don’t kill it — start on another port (--port <N>, e.g. 8082). Each expo start --tunnel gets its own tunnel URL, so ports coexist. Only reuse a running Metro if you started it this session.
(Mode C) expo start / node killed with exit 137
137 = SIGKILL — almost always the OOM killer (memory pressure, common in constrained cloud sandboxes, esp. a native build + Metro at once). Not a port clash.
Reduce memory pressure: don’t run a native build and Metro concurrently; give the sandbox more memory; retry.
(Mode C) Edits won’t live-reload no matter how often you reconnect
A release build is installed — its JS is baked in, so it ignores Metro
Stop reconnecting: install the dev (Debug) build, connect it to Metro, reload. Reconnecting a release build to Metro is a no-op.
expo start --tunnel errors for a robot/EXPO_TOKEN user
The ngrok robot-user guard
Use tunnel v2: EXPO_UNSTABLE_TUNNEL_V2=1 expo start --tunnel.
Unexpected charges / a session you forgot
start --non-interactive does NOT auto-stop
Always npx --yes eas-cli@latest simulator:stop --id <id>. List leftovers with npx --yes eas-cli@latest simulator:list.
Screenshot shows old content / my recent edits don't appear
Running a release build (Mode A/B) whose JS was baked in before your edits — typically a reused/stale build
A/B reflect code at build time, not now. Rebuild (ensure the build’s fingerprint matches current source), or use Mode C (dev + Metro) so live edits show via Fast Refresh. The screenshot itself is fresh — it’s the build that’s stale. (9:41 in the status bar is the sim default, not staleness.)
(argent) Every argent run/tools call returns 401 Unauthorized right after linking
argent link without --yes no-ops on an already-linked URL (“Already linked. No changes.”), keeping a stale token from a previous session
Re-link with --yes so the new token is written — see the link command in controllers.md.