Fix non-determinism errors in Output SDK workflows. Use when seeing replay failures, inconsistent results between runs, "non-deterministic" error messages, or workflows behaving differently on retry.
Scanned 5/27/2026
Install via CLI
openskills install growthxai/output---
name: output-error-nondeterminism
description: Fix non-determinism errors in Output SDK workflows. Use when seeing replay failures, inconsistent results between runs, "non-deterministic" error messages, or workflows behaving differently on retry.
allowed-tools: [Bash, Read]
---
# Fix Non-Determinism Errors
## Overview
This skill helps diagnose and fix non-determinism errors in Output SDK workflows. Workflows must be deterministic because Temporal may replay them during recovery or retries, and the replay must produce identical results.
## When to Use This Skill
You're seeing:
- "non-deterministic" error messages
- Replay failures after workflow restart
- Inconsistent results between runs with same input
- Errors during workflow recovery
- Warnings about determinism violations
## Root Cause
Temporal workflows must be deterministic: given the same input, they must always execute the same sequence of operations. This is because Temporal replays workflow history to recover state after crashes or restarts.
Non-deterministic operations break this replay mechanism because they produce different values each time.
## Common Causes and Solutions
### 1. Math.random()
**Problem**: Random values differ on each execution.
```typescript
// WRONG: Non-deterministic
export default workflow( {
fn: async input => {
const id = Math.random().toString( 36 ); // Different each time!
return await processWithId( { id } );
}
} );
```
**Solution**: Pass random values as workflow input or generate in a step.
```typescript
// Option 1: Pass as input
export default workflow( {
inputSchema: z.object( {
id: z.string() // Generate ID before calling workflow
} ),
fn: async input => {
return await processWithId( { id: input.id } );
}
} );
// Option 2: Generate in a step (steps can be non-deterministic)
export const generateId = step( {
name: 'generateId',
fn: async () => ( { id: Math.random().toString( 36 ) } )
} );
export default workflow( {
fn: async input => {
const { id } = await generateId( {} );
return await processWithId( { id } );
}
} );
```
### 2. Date.now() / new Date()
**Problem**: Timestamps change between executions.
```typescript
// WRONG: Non-deterministic
export default workflow( {
fn: async input => {
const timestamp = Date.now(); // Different each replay!
return await logEvent( { timestamp } );
}
} );
```
**Solution**: Pass timestamps as input or use Temporal's time API.
```typescript
// Option 1: Pass as input
export default workflow( {
inputSchema: z.object( {
timestamp: z.number()
} ),
fn: async input => {
return await logEvent( { timestamp: input.timestamp } );
}
} );
// Option 2: Generate in a step
export const getTimestamp = step( {
name: 'getTimestamp',
fn: async () => ( { timestamp: Date.now() } )
} );
```
### 3. crypto.randomUUID()
**Problem**: UUIDs differ each execution.
```typescript
// WRONG: Non-deterministic
import { randomUUID } from 'crypto';
export default workflow( {
fn: async input => {
const requestId = randomUUID(); // Different each time!
return await makeRequest( { requestId } );
}
} );
```
**Solution**: Generate UUIDs as input or in steps.
```typescript
// Correct: Generate in step
export const generateRequestId = step( {
name: 'generateRequestId',
fn: async () => {
const { randomUUID } = await import( 'crypto' );
return { requestId: randomUUID() };
}
} );
```
### 4. Dynamic Imports
**Problem**: Dynamic imports may resolve differently.
```typescript
// WRONG: Non-deterministic import timing
export default workflow( {
fn: async input => {
const module = await import( `./handlers/${input.type}` );
return module.handle( input );
}
} );
```
**Solution**: Use static imports and conditional logic.
```typescript
// Correct: Static imports with conditional use
import { handleTypeA } from './handlers/typeA';
import { handleTypeB } from './handlers/typeB';
export default workflow( {
fn: async input => {
if ( input.type === 'A' ) {
return await handleTypeA( input );
} else {
return await handleTypeB( input );
}
}
} );
```
### 5. Environment Variables
**Problem**: Environment may differ between replays.
```typescript
// WRONG: Environment can change
export default workflow( {
fn: async input => {
const apiUrl = process.env.API_URL; // May differ on different workers
return await callApi( { url: apiUrl } );
}
} );
```
**Solution**: Pass configuration as input or use constants.
```typescript
// Correct: Pass as input
export default workflow( {
inputSchema: z.object( {
apiUrl: z.string()
} ),
fn: async input => {
return await callApi( { url: input.apiUrl } );
}
} );
```
## How to Find Non-Deterministic Code
### Search for Common Patterns
```bash
# Find Math.random usage
grep -rn "Math.random" src/workflows/
# Find Date.now or new Date
grep -rn "Date.now\|new Date" src/workflows/
# Find crypto random functions
grep -rn "randomUUID\|randomBytes" src/workflows/
# Find dynamic imports
grep -rn "import(" src/workflows/
```
### Review Workflow Files
Look at your workflow `fn` functions specifically. Non-deterministic code is only a problem **in workflow functions**, not in step functions.
## Verification Steps
1. **Fix the code** using solutions above
2. **Run the workflow**: `npx output workflow run <name> '<input>'`
3. **Run again with same input**: Result should be identical
4. **Check for errors**: No "non-deterministic" messages
## The Determinism Rule
**Workflow functions must be deterministic:**
- Same input = same execution path
- No side effects (network, filesystem, random values)
- Only orchestration logic and step calls
**Step functions can be non-deterministic:**
- Steps record their results in Temporal history
- Replays use recorded results, not re-execution
- All I/O should happen in steps
## Debugging Tip
If unsure whether code is causing issues:
```bash
# Run the workflow
npx output workflow start my-workflow '{"input": "test"}'
# Get the workflow ID and run debug to see replay behavior
npx output workflow debug <workflowId> --format json
```
Look for errors or warnings about non-determinism in the trace.
## Related Issues
- For I/O in workflow code, see `output-error-direct-io`
- For random values needed in logic, generate them in steps or pass as input
No comments yet. Be the first to comment!