• Projects
  • Service
  • About
  • branding.bz
  • Podcast
  • Tips
  • FAQ
  • Recruit
  • Download
  • Contact
  • branding.bz(ブランド構築SaaS)
  • DESIGN NOW(デザインメディア)
  • X
  • LinkedIn
  • Spotify
  • Facebook

213-0011 神奈川県川崎市高津区久本3-6-7-303

© 2026 ID INC. All rights reserved

claude-skills/スキル
SKILLOfficialdevelopment

output-error-nondeterminism

プラグイン
outputai
ソース
GitHub で見る ↗
説明

Output SDK ワークフロー(自動処理の流れ)における非決定性エラー(実行するたびに結果が異なるエラー)を修正します。 次のような場合に使用: - リプレイ(実行の再現)に失敗している - 実行のたびに結果が異なる - 「非決定性」というエラーメッセージが表示される - ワークフローが再試行時に異なる動作をしている

原文を表示

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.

ユースケース
  • リプレイ実行に失敗している
  • 実行のたびに結果が異なる
  • 非決定性エラーメッセージが表示される
  • ワークフロー再試行時に異なる動作をしている
本文(日本語訳)

非決定論的エラーの修正

概要

このスキルは、Output SDK ワークフロー(一連の処理手順)における非決定論的エラー(同じ条件なのに異なる結果になるエラー)の診断と修正を支援します。ワークフローは決定論的である必要があります。なぜなら Temporal(ワークフロー実行基盤)は復旧やリトライ時にワークフロー履歴を再実行し、その再実行結果が元と同じでなければならないためです。

次のような場合に使用

以下のような状況が発生している場合:

  • 「非決定論的」というエラーメッセージが表示される
  • ワークフロー再開後にリプレイ(処理の再現)に失敗する
  • 同じ入力値でも実行結果が異なる
  • ワークフロー復旧時にエラーが起きる
  • 決定論的な実行に関する警告が出ている

根本原因

Temporal ワークフローは決定論的である必要があります。つまり、同じ入力が与えられれば、常に同じ一連の操作を実行する必要があります。これは Temporal がクラッシュや再起動後に状態を復旧するため、ワークフロー履歴を再度実行する必要があるためです。

非決定論的な操作(実行のたびに異なる値を生成する処理)は、この再実行メカニズムを破壊します。

よくある原因と解決策

1. Math.random()

問題: 乱数は実行のたびに異なります。

// ❌ 間違い: 非決定論的
export default workflow( {
  fn: async input => {
    const id = Math.random().toString( 36 );  // 毎回異なる!
    return await processWithId( { id } );
  }
} );

解決策: 乱数をワークフロー入力として渡すか、ステップ内で生成してください。

// 方法1: 入力値として渡す
export default workflow( {
  inputSchema: z.object( {
    id: z.string()  // ワークフロー呼び出し前に ID を生成
  } ),
  fn: async input => {
    return await processWithId( { id: input.id } );
  }
} );

// 方法2: ステップ内で生成(ステップは非決定論的でも構いません)
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()

問題: タイムスタンプ(時刻情報)は実行のたびに変わります。

// ❌ 間違い: 非決定論的
export default workflow( {
  fn: async input => {
    const timestamp = Date.now();  // 再実行のたびに異なる!
    return await logEvent( { timestamp } );
  }
} );

解決策: タイムスタンプを入力値として渡すか、Temporal の時刻 API を使用してください。

// 方法1: 入力値として渡す
export default workflow( {
  inputSchema: z.object( {
    timestamp: z.number()
  } ),
  fn: async input => {
    return await logEvent( { timestamp: input.timestamp } );
  }
} );

// 方法2: ステップ内で生成
export const getTimestamp = step( {
  name: 'getTimestamp',
  fn: async () => ( { timestamp: Date.now() } )
} );

3. crypto.randomUUID()

問題: UUID(一意識別子)は実行のたびに異なります。

// ❌ 間違い: 非決定論的
import { randomUUID } from 'crypto';

export default workflow( {
  fn: async input => {
    const requestId = randomUUID();  // 毎回異なる!
    return await makeRequest( { requestId } );
  }
} );

解決策: UUID を入力値またはステップ内で生成してください。

// ✅ 正しい方法: ステップ内で生成
export const generateRequestId = step( {
  name: 'generateRequestId',
  fn: async () => {
    const { randomUUID } = await import( 'crypto' );
    return { requestId: randomUUID() };
  }
} );

4. 動的インポート

問題: 動的インポート(実行時にモジュールを読み込む)は異なる結果になる可能性があります。

// ❌ 間違い: 非決定論的なインポートタイミング
export default workflow( {
  fn: async input => {
    const module = await import( `./handlers/${input.type}` );
    return module.handle( input );
  }
} );

解決策: 静的インポート(事前に全て読み込む)と条件分岐を使用してください。

// ✅ 正しい方法: 静的インポートと条件分岐
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. 環境変数

問題: 環境変数は再実行時に異なる可能性があります。

// ❌ 間違い: 環境が変わる可能性がある
export default workflow( {
  fn: async input => {
    const apiUrl = process.env.API_URL;  // 異なるワーカーで異なる可能性あり
    return await callApi( { url: apiUrl } );
  }
} );

解決策: 設定を入力値として渡すか、定数を使用してください。

// ✅ 正しい方法: 入力値として渡す
export default workflow( {
  inputSchema: z.object( {
    apiUrl: z.string()
  } ),
  fn: async input => {
    return await callApi( { url: input.apiUrl } );
  }
} );

非決定論的なコードの見つけ方

よくあるパターンを検索

# Math.random の使用箇所を検索
grep -rn "Math.random" src/workflows/

# Date.now または new Date を検索
grep -rn "Date.now\|new Date" src/workflows/

# 暗号化ライブラリの乱数生成を検索
grep -rn "randomUUID\|randomBytes" src/workflows/

# 動的インポートを検索
grep -rn "import(" src/workflows/

ワークフローファイルを確認

ワークフローの fn 関数を特に見てください。非決定論的なコードが問題になるのはワークフロー関数内だけで、ステップ関数内では問題になりません。

確認ステップ

  1. コードを修正 する(上記の解決策を使用)
  2. ワークフローを実行: npx output workflow run <名前> --input '<入力値>'
  3. 同じ入力値で再実行: 結果が同じである必要があります
  4. エラーを確認: 「非決定論的」というメッセージがないこと

決定論的実行の法則

ワークフロー関数は決定論的である必要があります:

  • 同じ入力 = 同じ実行順序
  • 副作用なし(ネットワークアクセス、ファイル操作、乱数生成など)
  • オーケストレーション(処理の調整)とステップ呼び出しのみ

ステップ関数は非決定論的でも構いません:

  • ステップはその実行結果を Temporal の履歴に記録します
  • 再実行時は記録された結果を使用し、処理を再度実行しません
  • すべての入出力はステップ内で行うべきです

デバッグのコツ

コードが問題を起こしているか不明な場合:

# ワークフローを実行
npx output workflow start my-workflow --input '{"input": "test"}'

# ワークフロー ID を取得し、デバッグを実行して再実行の動作を確認
npx output workflow debug <workflowId> --json

トレース(実行ログ)で非決定論的な実行に関するエラーや警告を確認してください。

関連する問題

  • ワークフロー内での入出力については、output-error-direct-io を参照してください
  • ロジック内で必要な乱数は、ステップ内で生成するか入力値として渡してください
原文(English)を表示

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:

# Run the workflow
npx output workflow start my-workflow --input '{"input": "test"}'

# Get the workflow ID and run debug to see replay behavior
npx output workflow debug <workflowId> --json

Look for errors or warnings about non-determinism in the trace.

Related Issues

  • For I/O in workflow code, see output-error-direct-io
  • For random values needed in logic, generate them in steps or pass as input

原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。