# Exit codes and errors

What each squiggle exit code means, so a CI job can act on the number. Plus squiggle explain, which turns an exit code or an error code into the cause and a command that fixes it.

## Exit codes

Every `squiggle` command exits with one of these codes. They don’t change between versions, and `squiggle --schema-json` lists them too.

| Code | Meaning |
| --- | --- |
| `0` | Success. There may be warnings. |
| `1` | The run failed: a flow, a suite, a gate or a diff. Also used for an unexpected error in Squiggle itself, which prints a stack trace. |
| `2` | A usage error, or a failure Squiggle expected, with a message that says what to do. |
| `3` | A licence is needed, has expired, or is over its CI limit. |
| `4` | The `--timeout` you set ran out. |
| `5` | No device is available, or another run is using it. |
| `6` | Needs an agent: an `agent:` step had no recording that worked, and no agent was connected. No step failed. Pass `--needs-agent fail` to exit 1 instead. |

## What CI should do with each

Codes 0, 1 and 6 are results: the flows ran. The others mean they couldn’t run properly, so fix the machine or the job before you trust the result. Pick what happened to see the code and what `squiggle explain` says about it:

## Explain a code

Pass an exit code or an error code to `squiggle explain`. It prints what went wrong and, when there is one, a command that fixes it. It works without a licence.

```shell
squiggle explain 5
squiggle explain E_STALE_DYLIB
```

Run `squiggle explain` on its own to list every code. Add `--json` to get the same thing as data.

## Error codes

| Code | What happened, and the fix |
| --- | --- |
| `E_NO_DEVICE` | No simulator is booted (exit 5). Boot one, or pass --udid to choose one. |
| `E_NOT_INJECTED` | squiggle up launched the app, but Squiggle didn’t attach to it (exit 5). Quit the app completely and run squiggle up again. |
| `E_STALE_DYLIB` | The app is running an older copy of Squiggle than this build (exit 5). Quit the app, then run squiggle up. |
| `E_NO_HOST` | The squiggle-host helper that drives the simulator wasn’t found (exit 5). Reinstall Squiggle, or run with --headless. |
| `E_CONNECT` | The app never connected on the port the command was waiting on (exit 2). If the message says `no app … connected … within 30 s, and this run does not relaunch it`, the app isn’t running, often because it crashed, and the run (a bench, a run of several flows, or a report card) doesn’t relaunch it. Run squiggle up, or the flow again with --fresh. Otherwise, run squiggle doctor to check the app is set up and which port it connects to. |
| `E_TIMEOUT` | The --timeout ran out (exit 4). Raise it, or give the command less to do. |
| `E_FLOW_PARSE` | The flow file isn’t valid YAML, or a step is malformed (exit 2). The message names the line and column. Quote any value that contains #. |
| `E_RUNS_WITH` | squiggle flow was given a program whose runWith: names another command, such as squiggle agent branch (exit 2). Run it with the command the message prints. |
| `E_BAD_CHAOS` | --chaos or --twin named a profile that doesn’t exist, or --twin none (exit 2). The profiles are slow, flaky, truncated and offline-midstep; --chaos also takes none, and --twin takes all. |
| `E_NO_STUBS` | --stubs named a run that isn’t in .squiggle/runs, or one that made no requests (exit 2). Run the flow once with --capture-net, and pass the run id its footer prints. |
| `E_BAD_RUN_COUNT` | --fuzz-max or --race-max wasn’t a whole number of runs, 1 or more (exit 2). |
| `E_TWIN_CLASH` | --twin, --fuzz or --cache-twins was combined with a flag that also chooses the network or the starting state, such as --chaos, --stubs or --race (exit 2). Pass one of them. |
| `E_RACE_CLASH` | --race was combined with --chaos or --stubs (exit 2). --race sets every request’s delay itself; drop the other flag. |
| `E_BAD_CLIFF` | --cliff wasn’t VAR=lo..hi, such as N=10..800 (exit 2). |
| `E_BAD_TUNE_WEIGHTS` | --tune-weights named a term other than dropped, blank or ms, or a weight that isn’t a number ≥ 0 (exit 2). |
| `E_TUNE_CLASH` | --tune was combined with --fresh (exit 2). A relaunch puts each tunable back to its default; drop --fresh. |
| `E_BISECT_STATE_CLASH` | --bisect-state was combined with a flag that also chooses what each run starts from, such as --fresh, --cache-twins, --tune or --twin (exit 2). Pass one of them. |
| `E_MATRIX_PLATFORM` | --matrix was run against a Mac app (exit 2). Its cells are simulator settings, so it needs an iOS simulator. |
| `E_BISECT_NATIVE` | A commit in the squiggle bisect range changes native code or a dependency, which Metro can’t serve (exit 2). Bisect from after that commit with --good, or rebuild and bisect across it by hand. |
| `E_BISECT_USAGE` | squiggle bisect got a range it can’t use: --good isn’t an ancestor of --bad, or the range is empty (exit 2). |
| `E_BAKEOFF_USAGE` | squiggle bakeoff got an empty --against or a ref that isn’t a commit, or found no app.json (exit 2). Pass --app-dir when the Expo app isn’t at the project root. |
| `E_BAKEOFF_NATIVE` | A squiggle bakeoff branch differs from your checkout in native code or a dependency, which Metro can’t serve (exit 2). Compare branches that differ only in JavaScript. |
| `E_BAKEOFF_NO_RUN` | squiggle bakeoff started the flow on a revision, but it never ran, for example because the app wasn’t running or the flow name is wrong (exit 1). The error above it says why; fix that and run again. |
| `E_CONTRACTS_PR` | squiggle contracts propose --pr couldn’t commit, push or open the pull request; the message shows what git or gh said (exit 1). Run it with --write instead and commit the flows yourself. |
| `E_NO_AGENT` | squiggle motion found no Squiggle agent in the app (exit 5). Run squiggle up. |
| `E_MOTION_CAPTURE` | squiggle motion couldn’t record the view around the tap, for example because the control to tap wasn’t on screen (exit 1). |
| `E_MOTION_MATCH` | The file given to squiggle motion --match couldn’t be read (exit 2). It takes the output of an earlier squiggle motion --json. |
| `E_PROBE_RULE` | A squiggle probe rule isn’t one (exit 2): it needs an input, a comparison and a number, such as render.max >= 30, and the input must be one that squiggle probe inputs lists. |
| `E_PROBE_UNSUPPORTED` | The app doesn’t take probes (exit 5): it is a release build, which never runs one, or its Squiggle SDK is older than probes. |
| `E_PROBE_REFUSED` | The app refused the probe and said why (exit 2), for example a range wider than the input’s, more than 256 instructions in one probe, or more than eight probes at once. |
| `E_PROBE_NO_FIRINGS` | squiggle probe export found no recorded firings for that id (exit 2). Run the probe with squiggle probe when first, with the same rules and --id. |
| `E_PROBE_MISMATCH` | The rules given to squiggle probe export aren’t the ones the probe ran with (exit 2): re-running them over the recorded firings gives different answers. Pass the same rules, --any and --component as the probe used. |
| `E_LICENSE_REQUIRED` | No licence or trial on this Mac (exit 3). Run squiggle activate --trial, or activate a key. |
| `E_LICENSE_EXPIRED` | The trial or licence has ended (exit 3). Run squiggle activate with a current key. |
| `E_CI_LIMIT` | Too many CI runners are using this licence at once (exit 3). Wait for one to finish, or see your plan’s limit with squiggle license. |

For problems that don’t produce an error code, see [Troubleshooting](https://squiggle.sh/docs/troubleshooting).
