| 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.
export default workflow({
fn: async (input) => {
const id = Math.random().toString(36);
return await processWithId({ id });
}
});
Solution: Pass random values as workflow input or generate in a step.
export default workflow({
inputSchema: z.object({
id: z.string()
}),
fn: async (input) => {
return await processWithId({ id: input.id });
}
});
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.
export default workflow({
fn: async (input) => {
const timestamp = Date.now();
return await logEvent({ timestamp });
}
});
Solution: Pass timestamps as input or use Temporal's time API.
export default workflow({
inputSchema: z.object({
timestamp: z.number()
}),
fn: async (input) => {
return await logEvent({ timestamp: input.timestamp });
}
});
export const getTimestamp = step({
name: 'getTimestamp',
fn: async () => ({ timestamp: Date.now() })
});
3. crypto.randomUUID()
Problem: UUIDs differ each execution.
import { randomUUID } from 'crypto';
export default workflow({
fn: async (input) => {
const requestId = randomUUID();
return await makeRequest({ requestId });
}
});
Solution: Generate UUIDs as input or in steps.
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.
export default workflow({
fn: async (input) => {
const module = await import(`./handlers/${input.type}`);
return module.handle(input);
}
});
Solution: Use static imports and conditional logic.
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.
export default workflow({
fn: async (input) => {
const apiUrl = process.env.API_URL;
return await callApi({ url: apiUrl });
}
});
Solution: Pass configuration as input or use constants.
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
grep -rn "Math.random" src/workflows/
grep -rn "Date.now\|new Date" src/workflows/
grep -rn "randomUUID\|randomBytes" src/workflows/
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
- Fix the code using solutions above
- Run the workflow:
npx output workflow run <name> '<input>'
- Run again with same input: Result should be identical
- 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:
npx output workflow start my-workflow '{"input": "test"}'
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