Skip to content
Development
Skill

/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.

From plugin
output
43052 skills11 agents1 command
Install
$ npx -y skills add growthxai/output --skill output-error-nondeterminism --agent claude-code

How 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.md
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:

Read more
Ships withoutput

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.

Get the whole plugin