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…
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.
Claude Code
$ npx -y skills add growthxai/output --skill output-error-nondeterminism --agent claude-code
Codex
$ npx skills add growthxai/output --skill output-error-nondeterminism -a codex -y
Cursor
$ npx skills add growthxai/output --skill output-error-nondeterminism -a cursor -y
Gemini CLI
$ npx skills add growthxai/output --skill output-error-nondeterminism -a gemini-cli -y
GitHub Copilot
$ npx skills add growthxai/output --skill output-error-nondeterminism -a github-copilot -y
OpenCode
$ npx skills add growthxai/output --skill output-error-nondeterminism -a opencode -y
Windsurf
$ npx skills add growthxai/output --skill output-error-nondeterminism -a windsurf -y
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]
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.
You're seeing:
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.
**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 } );
}
} );**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() } )
} );**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() };
}
} );**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 );
}
}
} );**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 } );
}
} );# 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/Look at your workflow `fn` functions specifically. Non-deterministic code is only a problem **in workflow functions**, not in step functions.
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
**Workflow functions must be deterministic:**
**Step functions can be non-deterministic:**
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.
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an…
View, edit, and set encrypted credentials in an Output.ai project. Use when adding secrets, updating API keys, verifying…
Wire encrypted credentials to environment variables using the credential: convention. Use when setting up LLM provider keys…
Initialize encrypted credentials for an Output.ai project. Use when setting up credentials for the first time, adding…
Debug Output SDK workflow issues. Use when user reports a workflow failing, erroring, hanging, producing wrong results, or asks…
Use the Agent class for multi-step tool loops, conversation history, streaming progress, and reusable LLM agents. Use when…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.