From outputai
Fixes non-determinism errors in Output SDK workflows from Math.random(), Date.now(), or crypto.randomUUID(). Use for replay failures, inconsistent runs, or determinism violations.
npx claudepluginhub growthxai/output --plugin outputaiThis 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.
Fixes direct I/O errors in Output SDK workflow functions causing hangs, undefined returns, determinism violations, or failed HTTP/DB ops. Moves operations to steps.
Guides durable workflow automation for AI agents using n8n, Temporal, Inngest, AWS Step Functions. Covers sequential/parallel patterns for reliable production tasks.
Guides workflow automation with platforms like n8n, Temporal, Inngest for durable execution, event-driven patterns, and reliable AI agents in production.
Share bugs, ideas, or general feedback.
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-io