install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/xstate-typegen" ~/.claude/skills/intense-visions-harness-engineering-xstate-typegen-e62387 && rm -rf "$T"
manifest:
agents/skills/codex/xstate-typegen/SKILL.mdsource content
XState Typegen
Generate full type safety for XState machines with typegen (v4) and the setup pattern (v5)
When to Use
- Getting TypeScript errors about missing event types in guards or actions
- Wanting autocompletion for state names, event types, and action names
- Ensuring type safety between machine definition and service implementations
- Setting up a new XState project with strict typing from the start
Instructions
XState v5 (recommended): setup pattern
- Use
to declare all types, actions, guards, actors, and delays before creating the machine.setup() - Define
with TypeScripttypes
assertion for context, events, input, and output.as - All actions, guards, and actors referenced in the machine must be declared in
— runtime errors if missing.setup() - The machine is fully typed from the
declaration — no separate code generation step.setup()
// auth.machine.ts (v5) import { setup, assign, fromPromise } from 'xstate'; interface AuthContext { user: { id: string; name: string } | null; error: string | null; } type AuthEvent = { type: 'LOGIN'; email: string; password: string } | { type: 'LOGOUT' }; const authMachine = setup({ types: {} as { context: AuthContext; events: AuthEvent; input: { redirectUrl?: string }; }, actors: { authenticate: fromPromise(async ({ input }: { input: { email: string; password: string } }) => { const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify(input), }); if (!res.ok) throw new Error('Auth failed'); return res.json() as Promise<{ id: string; name: string }>; }), }, actions: { setUser: assign({ user: (_, params: { id: string; name: string }) => params, }), clearUser: assign({ user: null, error: null }), setError: assign({ error: (_, params: { message: string }) => params.message, }), }, guards: { isAuthenticated: ({ context }) => context.user !== null, }, }).createMachine({ id: 'auth', initial: 'idle', context: ({ input }) => ({ user: null, error: null, }), states: { idle: { on: { LOGIN: 'authenticating' }, }, authenticating: { invoke: { src: 'authenticate', input: ({ event }) => ({ email: (event as { email: string }).email, password: (event as { password: string }).password, }), onDone: { target: 'authenticated', actions: { type: 'setUser', params: ({ event }) => event.output }, }, onError: { target: 'error', actions: { type: 'setError', params: ({ event }) => ({ message: (event.error as Error).message }), }, }, }, }, authenticated: { on: { LOGOUT: { target: 'idle', actions: 'clearUser' } }, }, error: { on: { LOGIN: 'authenticating' }, }, }, });
XState v4: typegen
- Install
:@xstate/cli
.npm install -D @xstate/cli - Add
to the machine config to enable typegen.tsTypes: {} - Run
to generatexstate typegen "src/**/*.machine.ts"
files..typegen.ts - The generated file provides exact types for which events are valid in which states, which services are invoked, and which actions/guards are used.
// v4 with typegen import { createMachine } from 'xstate'; const machine = createMachine({ tsTypes: {} as import('./auth.machine.typegen').Typegen0, schema: { context: {} as AuthContext, events: {} as AuthEvent, services: {} as { authenticate: { data: User } }, }, // ... });
Details
v5 setup benefits over v4 typegen:
- No code generation step — types flow from
directlysetup() - Actions and guards must exist at declaration time — catches typos immediately
- Actor input/output types are inferred from
,fromPromise
, etc.fromCallback - No
files to maintain or commit.typegen.ts
Typing event narrowing in actions: In v5, action implementations receive the full event union. Narrow when needed:
actions: { handleLogin: ({ event }) => { if (event.type === 'LOGIN') { console.log(event.email); // Typed correctly } }, },
Common type issues:
in transitions — usually means the event is not listed in the events typeType 'string' is not assignable to type 'never'- Missing action/guard in
— v5 requires all referenced names to be declared upfrontsetup() - Circular type inference — break cycles by explicitly typing
function returncontext
Source
https://stately.ai/docs/typescript
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.