XState v5 shipped in December 2023, and its migration guide lists a breaking change that is easy to miss: implementation functions now take a single object argument with context, event and other properties.

Code written in the v4 style still looks fine. Here is what it does on xstate 5.33.2.

Before: v4 style on v5

import { createMachine, createActor, assign } from "xstate"; const machine = createMachine({ initial: "red", context: { cycles: 0 }, states: { red: { on: { NEXT: { target: "green", actions: assign({ cycles: (context, event) => context.cycles + 1 }), }, }, }, green: {}, }, }); const actor = createActor(machine); actor.start(); actor.send({ type: "NEXT" }); console.log(actor.getSnapshot().value); // "green" console.log(actor.getSnapshot().context.cycles); // NaN 

No exception. The machine moves to the next state, and the counter is quietly wrong. In v5, the first argument is { context, event }, so context.cycles is undefined.

After: the v5 version

actions: assign({ cycles: ({ context }) => context.cycles + 1 }) 

The tested recipe

The ProvenPin recipe xstate-v5-traffic-light models a traffic light with createMachine, createActor, guards and context updates. It pins xstate 5.33.2, tsx 4.23.15 and @types/node 26.6.5, and passed 2 of 2 tests in CI on Oct 18, 2026 on Node v22.23.3. The receipt and full files are here: https://provenpin.com/recipes/xstate-v5-traffic-light/

Use it from your coding agent

ProvenPin is a free MCP server. No API key, no signup.

claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp 

Cursor (~/.cursor/mcp.json):

{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } } 

Then ask your agent for "a tested XState v5 recipe", or call get_recipe("xstate-v5-traffic-light"). Setup for VS Code, Windsurf and Cline is in the docs.

Sources: XState migration guide, recipe receipt.