Find the app state that breaks a flow

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

A flow passes from one saved state and fails from another, and the two differ in a dozen values. Squiggle finds the few that make it fail, and names the lines that write them and the components that read them.

This needs an app that squiggle up has prepared, with its state in Zustand or Legend State stores, which is what a save slot holds. Each run starts from the state Squiggle loads, so the flow doesn’t relaunch the app, and its prerequisite is skipped.

Save the two states

Put the app in a state the flow passes from and save it, then in one it fails from and save that:

Terminal
squiggle save state-lab-greensquiggle save state-lab-failing

Take both on the same screen, where the flow starts. The search loads the slots on the device the flow runs on: the one you pass with --udid, or the booted simulator. See Save and load app state.

Find the values that matter

Terminal
squiggle flow state-lab --bisect-state state-lab-green,state-lab-failingbisect state state-lab · state-lab-green against state-lab-failing · 6 candidate(s)field                         state-lab-green  state-lab-failing    cause────────────────────────────  ───────────────  ───────────────────  ─────zustand:useStateLab.coupon    ""               "SPRING"             yeszustand:useStateLab.currency  "EUR"            "USD"zustand:useStateLab.giftWrap  false            truezustand:useStateLab.note      ""               "leave at the door"zustand:useStateLab.quantity  1                3zustand:useStateLab.region    "eu"             "us"  ✔ found: the failure needs zustand:useStateLab.coupon, 1 of 6 candidates  ! restores: 7, against 10 for one candidate at a time  ! useStateLab.coupon: declared at apps/example/src/store/stateLab.ts:64  ! useStateLab.coupon: written at apps/example/src/store/stateLab.ts:73 (loadReturningCart)  ! useStateLab.coupon: written at apps/example/src/store/stateLab.ts:76 (setCoupon)  ! useStateLab.coupon: read by CouponRow at src/app/state-lab.tsx:36  ! useStateLab.coupon: read by OrderTotal at src/app/state-lab.tsx:45  .squiggle/agent/briefs/flow-state-bisect-state-lab-state-lab-green-state-lab-failing-zustand-usestatela.json

Squiggle runs the flow from each slot first, to check that one passes and the other fails. Then it loads the passing slot with some of the failing slot’s values put in, runs the flow again, and narrows to the smallest set of values that still fails. It loads that set once more to confirm the failure, and finishes by loading the passing slot, so the app is left in a state that works.

The table lists every value that differs between the two slots, and marks the ones the failure needs. Under it, for each of those, are the line that declares the store, each line that writes the value, and each component that reads it. The same findings are saved as a brief in .squiggle/agent/briefs/, where your coding agent picks them up with the briefs MCP tool. Agents can run the same search with the bisect_state MCP tool.

When it can’t say

The result saysWhat it means, and what to do
green-failsThe flow fails from the first slot too. Save a slot the flow passes from.
failing-passesThe flow passes from the second slot. The failure needs something a slot doesn’t hold, such as the network or timing.
outside-candidatesLoading every differing value still passes, so the cause is somewhere the slots don’t compare. MMKV storage is kept as it is in the first slot, and is named here when the slots hold any.
not-reproducedThe smallest set failed once and passed when loaded again. The flow doesn’t give the same result every time; make it repeatable first.
refusedYour app refused to load a state, or a slot name doesn’t exist. The reason follows.

The command exits 1 unless it finds the values. It can’t be combined with --fresh, --cache-twins, --tune, --twin, --fuzz or --matrix, which each choose what a run starts from.

Races that depend on state

Terminal
squiggle flow race-lab --race --bisect-state race-green,race-failingbisect state race-lab (duplicate-mutation) · race-green against race-failing · 4 candidate(s)  ✔ found: the failure needs zustand:useRaceLab.guardSubmit, 1 of 4 candidates  ! restores: 7, against 8 for one candidate at a time  ! useRaceLab.guardSubmit: written at apps/example/src/store/raceLab.ts:28 (toggleGuard)bisect state race-lab (navigation-after-back) · race-green against race-failing · 4 candidate(s)  ✗ green-fails: the flow fails from race-green too

Add --race to search for races from the failing slot. For each race it finds, Squiggle first shortens the flow to the fewest steps that still reproduce it, then searches the state with that shorter flow, so the brief names both. A race that happens from the passing slot too doesn’t depend on the state, and is reported as green-fails. See Find what breaks on a bad network for the race search itself.