/output-error-nondeterminism
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.
$ npx -y skills add growthxai/output --skill output-error-nondeterminism --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/output-error-nondeterminism
Context preview
The summary Claude sees to decide when to auto-load this skill.
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.
SKILL.md
output-error-nondeterminism.SKILL.mdname: 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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
# 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 '<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:
Read more
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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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
# 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 '<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:
The open-source TypeScript framework for building AI workflows and agents. Designed for Claude Code — describe what you want, Claude builds it, with all the best practices already in place. One framework.
Repo: growthxai/output
Other skills on output.
- /llm-output-schema-constraints
Zod schema constraints that Anthropic rejects or silently ignores when sent as structured-output tool definitions via Output.object(). Use when writing or reviewing Zod schemas passed to Output.object(), or debugging structured-output validation errors.
Open skill - /prompt-file-provider-options
Guide to the providerOptions structure in .prompt files — decision tree for where an option goes, common mistakes, per-provider quick reference, and Anthropic prompt caching. Use when writing or reviewing .prompt file frontmatter (provider, model, providerOptions,
Open skill - /validate
Run lint, build, and tests to validate changes are correct
Open skill - /output-build-workflow
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an existing plan, or after output-plan-workflow has produced a plan and the user is ready to build.
Open skill - /output-credentials-edit
View and edit encrypted credentials in an Output.ai project. Use when adding secrets, updating API keys, verifying credential values, or retrieving a specific credential.
Open skill - /output-credentials-env-vars
Wire encrypted credentials to environment variables using the credential: convention. Use when setting up LLM provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY) or any env var that should come from encrypted credentials.
Open skill

