Troubleshooting
For AI agents: this page is Markdown at https://squiggle.sh/docs/troubleshooting.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.
Fixes for the problems people run into most, listed by what you see. Start with squiggle doctor: it checks the simulator, your app, screen tracking, source lines and your licence, and names the fix for each problem it finds.
squiggle doctorInstall and set up
squiggle: command not found
If you installed Studio, install the command from it: open Settings (⌘ ,) and press Install next to Command line. Studio links squiggle into ~/.local/bin, and app updates update it too. If your shell still can’t find it, add ~/.local/bin to your PATH.
Metro can’t find a package in a monorepo
If your app is in a workspace and its dependencies are installed at the repository root, Metro only looks in the app’s own folder. Let it look at the root too, and keep withSquiggle around the config you export:
// metro.config.jsconst { getDefaultConfig } = require('expo/metro-config');const { withSquiggle } = require('@squiggle.sh/react-native/metro');const path = require('path');const projectRoot = __dirname;const workspaceRoot = path.resolve(projectRoot, '../..');const config = getDefaultConfig(projectRoot);config.watchFolders = [workspaceRoot];config.resolver.nodeModulesPaths = [ path.resolve(projectRoot, 'node_modules'), path.resolve(workspaceRoot, 'node_modules'),];module.exports = withSquiggle(config);pod install fails with Yarn 1
The SDK needs react-native-nitro-modules 0.35. Other package managers install it with the SDK; Yarn 1 only prints a warning. Add it yourself, then run pod install again:
yarn add react-native-nitro-modules@^0.35.9Running flows
No booted simulator, or the device is busy
no booted simulator found means no simulator is running. Boot one in Xcode or Studio, or pass --udid to choose one when several are running.
Only one run can use a simulator at a time. A second run stops with exit code 5:
squiggle flow checkoutanother squiggle run holds this device (pid 1234, started 40 s ago)Wait for the first run to finish, or run the second on another simulator.
Port 7777 is held by another program
:7777 is held by another Squiggle producer, not Studio names the process that holds the port. Quit it and run again; relaunching the app doesn’t free the port. If the message says Studio may be wedged, quit and reopen Studio.
If you can’t free the port, use another one. Set "port": 7790 in .squiggle/config.jsonc, or SQUIGGLE_PORT=7790 for one command, and point your app at the same port in its .env:
EXPO_PUBLIC_SQUIGGLE_WS_URL=ws://localhost:7790Restart Metro with --clear so the app picks it up.
A command drives Studio or another app instead of your simulator
With no --udid, squiggle save, squiggle load and your agent’s tools drive the app that Squiggle launched most recently. If that was Studio or another simulator, pass --udid with the simulator you mean, or run squiggle up for your app again so it is the newest. A flow, and anything it runs beside itself such as --tune or --bisect-state, always drives the booted simulator for its platform, or the one --udid names.
Runs are slow or flaky, or typing fails
Your app was probably launched without Squiggle attached, for example from Xcode or with expo run:ios on its own. Flows still run, but each run takes a few seconds longer. A step that waits for the app to settle can’t ask the app, so it moves on after a few unanswered checks, and the next step can act in the middle of an animation. Typing goes through the simulator’s keyboard instead, and a type step can fail with never took keyboard focus over HID or, on a busy Mac, keystrokes were dropped. doctor shows a warning on its app injected line.
Relaunch the app with Squiggle attached:
squiggle upTo stop it happening after every rebuild, end your ios script with && squiggle up. squiggle init does this for the two common scripts. Studio always launches the app with Squiggle attached.
A step waits a long time for the app to settle
Something on screen is still animating. Run the flow with --settle-debug to log every settle check. When a wait runs out, it names what kept it waiting: the component and its file:line, when Squiggle can find them, and the animated property.
squiggle flow checkout --settle-debugEvery step suddenly takes seconds after an update
The app is still running the copy of Squiggle it was launched with, from before you updated. doctor reports that the running copy doesn’t match this build, and a run can stop with E_STALE_DYLIB. Quit the app completely, then relaunch it:
xcrun simctl terminate booted <bundle-id>squiggle upA step passes, but nothing happened in the app
A passing tap means the tap reached the control, not that the app reacted. If nothing changed afterwards, the step shows the warning no graph reaction. Add an assert after the action to check the result. See How Squiggle works.
The second run fails at the first step
A flow starts from wherever the last run left the app. If the last run failed halfway, or ended on a different screen, the next one can’t find its first control, and the error often looks like a broken selector. Relaunch the app before the run:
squiggle flow login --freshTo avoid this for good, give the flow a starting state (see flows).
A tap fails because its target is off screen
Squiggle won’t tap a control that is outside the screen, because the tap would land on whatever is at the edge instead. The error names the control’s position and the screen size. Scroll to it first with a scrollUntilVisible step, or run with --auto-scroll to scroll automatically.
A flow won’t load: not valid YAML
The flow file isn’t valid YAML. The run stops with exit code 2 and E_FLOW_PARSE, and names the line and column:
squiggle flow ordersflow: not valid YAML — Flow map in block collection must be sufficiently indented and end with a } at line 6, column 1: visible: { text: Order #20 }The usual cause is a # in a value: YAML reads everything from # on as a comment. Inside { } that swallows the closing brace, which is the error above. On a line of its own it isn’t an error at all: text: Order #20 looks for Order. Quote any value with a # in it:
- assert: visible: { text: "Order #20" }A step passes normally but fails with --ax-only
The step fails with ax_unreachable: no element in the accessibility tree carries the selector, or several announce it the same way, so a VoiceOver user can’t reach the control. Give it an accessibilityLabel or a testID that names it uniquely. See Run settings.
A flow passes on its own but fails on the report card
The report card runs each flow offline, against stubs built from its last green run. A stub keeps each response’s shape but not its values: strings read like title 1 and numbers count up from 1. A flow that asserts text from your server, such as a product name, fails there. Run the card with --live to use your real backend, or assert on something the server doesn’t write, such as a testID.
The report card’s startup row stays pending
The row says which screens load at startup was not measured. Squiggle times the modules your app evaluates at startup only when your Metro config is wrapped in withSquiggle (see withSquiggle) and the bundle was built with it. Add it, restart Metro with --clear, and run the card again. Screens are read from Expo Router’s app/ route files, so an app without them always shows the row as pending.
The report card’s cliff row stays pending
The card reads the newest --cliff sweep and never runs one. The row stays pending until a sweep reaches the size the screen declares, and again whenever the screen’s route file, or the file of the component the sweep named, changes. Run the sweep the row’s fix names, with a range that goes past max. See Keep a screen off its cliff.
A report card row says the app was not running
The row reads no app on … connected … and this run does not relaunch it. The app crashed or exited during an earlier flow, and this flow waited 30 seconds for it before giving up. Any run that doesn’t relaunch the app itself, such as squiggle flows or a benchmark, stops the same way. A crash leaves a report in ~/Library/Logs/DiagnosticReports. The card starts the app again for the next flow, as it does after any failed flow. When you run one flow by itself, relaunch the app with squiggle up, or run the flow with --fresh.
The app reloads in the middle of a run
Two Metro servers are serving the same app, usually because expo start is running twice. The app loads the bundle from one and then reloads from the other. Stop every Metro server except one.
React Native DevTools disconnects during a run
Squiggle keeps the app’s debugger connection for the run, to capture a JavaScript profile when the JS thread stalls, and Metro allows one debugger per app, so DevTools disconnects until the run ends. Reconnect afterwards, or turn the capture off. See Profiling.
Saves and state
squiggle load refuses a save
A save is checked against your app before anything is restored. If a store or screen it holds has changed or gone, squiggle load refuses the whole save, names what changed, and leaves your app as it was. A save taken before your stores had names, which lists stores such as store:zustand-2, is refused the same way once they do. Save it again. See Save and restore your app’s state.
squiggle load fails on another simulator
MMKV data is copied on the simulator that took the save, so it loads only there. On another simulator, or after you reset the app’s data, the screen and stores are restored but every MMKV instance is left as it was, and the load reports failure naming the instance. Take the save again on this simulator.
Stores show as store:zustand-3 after an upgrade
Metro is serving a bundle built before the Babel plugin could name your stores. Restart it with --clear:
npx expo start --clearA store created inside a component keeps a numbered name. See Store names.
squiggle product state is empty
The app sends no state samples. That also leaves --where and friction --state with nothing to split by. Build the app with EXPO_PUBLIC_SQUIGGLE_STATE_ANALYTICS=1, debug builds included, and check that your app doesn’t pass false to setStateAnalyticsConsent. See Turn on state samples.
Studio and the Inspector
Screens show as “(no screen)”
Squiggle can’t see which screen is showing, so waiting for a screen, checking it and the Screen map don’t work. doctor warns on its route tracking line. Wrap your root in <Squiggle navigationRef={…}>, as in Set up your app.
The Inspector can’t show a component’s file and line
The Babel plugin isn’t in your build. doctor warns on its source locations line. Add @squiggle.sh/babel-plugin to babel.config.js (squiggle init does it), then rebuild the app. A reload isn’t enough.
The on-device overlay doesn’t appear
The overlay only draws in a development build, and only when the app isn’t connected to Studio or the CLI. When Studio or a flow launches the app, the overlay stays hidden, because Studio has its own Inspector. To use the overlay on its own, add EXPO_PUBLIC_SQUIGGLE_WS_URL=off to your app’s .env and launch the app yourself.
Still stuck
Run squiggle doctor --bundle. It writes a support file on your Mac with versions, settings and paths relative to your project, and none of your code. You can read it before you send it. Email it to support@squiggle.sh with what you did and what happened.