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.
Diagnose and fix non-determinism errors in Output SDK workflows that cause replay failures. Triggered when seeing "non-deterministic" errors, inconsistent results, or replay issues. Identifies and resolves common causes like Math.random(), Date.now(), and dynamic imports.
/plugin marketplace add growthxai/output-claude-plugins/plugin install growthxai-outputai-plugins-outputai@growthxai/output-claude-pluginsThis skill is limited to using the following tools:
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.
npx output workflow run <name> '<input>'Workflow functions must be deterministic:
Step functions can be non-deterministic:
If unsure whether code is causing issues:
# 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.
output-error-direct-ioThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.