Run in CI

For AI agents: this page is Markdown at https://squiggle.sh/docs/run/ci.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.

Run your flows on a macOS CI runner with a simulator. You need a licence token, a build of your app launched with squiggle up, and a job that reads the exit code.

A GitHub Actions job

.github/workflows/flows.yml
jobs:  flows:    runs-on: macos-15    env:      SQUIGGLE_LICENSE_TOKEN: ${{ secrets.SQUIGGLE_LICENSE_TOKEN }}    steps:      - uses: actions/checkout@v4      - run: npm install -g @squiggle.sh/cli      - run: npx expo run:ios && squiggle up      - run: squiggle flows .squiggle/flows --junit results.xml --events      - uses: actions/upload-artifact@v4        if: failure()        with:          name: squiggle-runs          path: .squiggle/runs/

Add your licence key as a secret named SQUIGGLE_LICENSE_TOKEN. A runner doesn’t use an activation, and doesn’t count against your Macs. Your plan limits how many runners use Squiggle at once; see Licence and trial.

Results CI can read

FlagWhat it does
--junit <file>Writes JUnit XML, which most CI systems show as test results.
--eventsPrints one JSON line per step for the log.
--continue-on-failRuns every step instead of stopping at the first failure.

squiggle flows passes any other squiggle flow flag on to every flow it runs. Every run writes one file to .squiggle/runs/, with each step’s timings, a flow.step.renders line per step with the components that rendered most during it, and a screenshot when it fails. Upload that folder as an artifact when the job fails.

Keep the app from getting worse

When your app is in a git repository, squiggle init adds .github/workflows/squiggle.yml at the repository root. It writes the file once and never overwrites it, so your edits stay. On every pull request the workflow builds your app’s iOS bundle at the pull request and at its base, then runs squiggle gate app over the two. The check fails when the bundle grew by more than 1 KiB or 5%, whichever is larger. It runs on a Linux runner, with no simulator and no licence.

Terminal
squiggle gate app --bundle "$RUNNER_TEMP/bundle-head" --base-bundle "$RUNNER_TEMP/bundle-base"

As written, the workflow builds each bundle with npx expo export --platform ios, so it works for Expo apps. For a bare React Native app, replace those two steps with your own bundle command, writing the iOS bundle (.js or .hbc) to _expo/static/js/ios/ inside the same folders. If the app was outside git when you ran init, run it again after git init to add the workflow.

On your Mac

Every squiggle card also records each screen’s budget numbers, its legibility score and each flow’s time, in .squiggle/gate/app.jsonl. Run squiggle gate app with no flags to compare the newest card with the newest green one recorded where your branch left origin/main. --base <ref> compares with another branch. If no card was recorded there yet, it compares with the previous card instead and says so. A card that wasn’t green is never the one compared against.

NumberHow much worse it may get
A screen’s budget metricA tenth of the limit its budget sets, so a screen with a tighter budget gets a tighter check. With no budget, a tenth of the number.
A screen’s legibility scoreNot at all.
A flow’s time250 ms or 25%, whichever is larger, since run times vary.
The bundle size1 KiB or 5%, whichever is larger.

gate app exits 1 when any number is worse by more than it may be, and 0 otherwise, including the first time, when there is nothing to compare with yet. It exits 2 when a folder given to --bundle or --base-bundle holds no iOS bundle.

Exit codes

Read the exit code, not the output. Codes 0, 1 and 6 are results; the others mean the runner couldn’t test properly. Pick what happened:

exit0

Green.

Terminal
squiggle explain 0exit 0  success (warnings allowed)

The full list is in Exit codes and errors.

Agentic flows in CI

With the .agent.json files committed, agentic flows replay in CI like any other flow: no agent, no model, no API key. A step that needs an agent stops the run with exit code 6, because nothing failed. To make the job fail instead, add --needs-agent fail.

A step needs an agent in CI when nothing is saved for it, or when its saved steps no longer reach the assert. Record it again on your Mac and commit the new .agent.json with the change that broke it. See Agentic flows.

Use more simulators

--shards 4 splits the suite across four booted simulators. --clone-from <udid> clones a simulator to make more. You can also set shards in config.jsonc, or in a ci profile that the job selects with --profile ci.