🔀output-error-nondeterminism
- プラグイン
- outputai
- ソース
- GitHub で見る ↗
説明
Output SDK ワークフローにおける非決定性エラーを修正します。 次のような場合に使用: - リプレイの失敗が発生している - 実行ごとに結果が一致しない - 「non-deterministic(非決定性)」というエラーメッセージが表示されている - リトライ時にワークフローの動作が異なる
原文を表示
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 はリカバリーやリトライ時にワークフローをリプレイすることがあるため、ワークフローは必ず決定性を持つ必要があります。リプレイは毎回同一の結果を生成しなければなりません。
次のような場合に使用
- 「non-deterministic」エラーメッセージが表示されている
- ワークフロー再起動後にリプレイが失敗する
- 同じ入力で実行するたびに異なる結果が得られる
- ワークフローのリカバリー中にエラーが発生する
- 決定性違反に関する警告が出ている
根本原因
Temporal のワークフローは決定性を持つ必要があります。つまり、同じ入力が与えられた場合、常に同じ順序で処理を実行しなければなりません。
これは、Temporal がクラッシュや再起動後の状態を復元するためにワークフローの履歴をリプレイするためです。
非決定性の処理は、実行のたびに異なる値を生成するため、このリプレイの仕組みを破壊します。
よくある原因と解決策
1. Math.random()
問題: ランダム値は実行のたびに異なります。
// 誤り: 非決定性
export default workflow( {
fn: async input => {
const id = Math.random().toString( 36 ); // 毎回異なる値になる!
return await processWithId( { id } );
}
} );
解決策: ランダム値はワークフローの入力として渡すか、step 内で生成します。
// 方法1: 入力として渡す
export default workflow( {
inputSchema: z.object( {
id: z.string() // ワークフロー呼び出し前に ID を生成する
} ),
fn: async input => {
return await processWithId( { id: input.id } );
}
} );
// 方法2: step 内で生成する(step は非決定性でも構わない)
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 の time API を使用します。
// 方法1: 入力として渡す
export default workflow( {
inputSchema: z.object( {
timestamp: z.number()
} ),
fn: async input => {
return await logEvent( { timestamp: input.timestamp } );
}
} );
// 方法2: step 内で生成する
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 を入力として渡すか、step 内で生成します。
// 正しい例: step 内で生成する
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/
# crypto のランダム関数を検索
grep -rn "randomUUID\|randomBytes" src/workflows/
# 動的インポートを検索
grep -rn "import(" src/workflows/
ワークフローファイルを確認する
ワークフローの fn 関数に絞って確認してください。
非決定性のコードが問題になるのは ワークフロー関数の中のみであり、step 関数の中では問題になりません。
検証手順
- コードを修正する — 上記の解決策を参照
- ワークフローを実行する:
npx output workflow run <name> '<input>' - 同じ入力で再実行する — 結果が同一であることを確認
- エラーを確認する — 「non-deterministic」メッセージが出ないことを確認
決定性のルール
ワークフロー関数は決定性を持たなければなりません:
- 同じ入力 = 同じ実行パス
- 副作用なし(ネットワーク、ファイルシステム、ランダム値など)
- オーケストレーションロジックと step の呼び出しのみ
step 関数は非決定性でも構いません:
- step は実行結果を Temporal の履歴に記録します
- リプレイ時は再実行ではなく、記録済みの結果を使用します
- すべての I/O 処理は step 内で行うべきです
デバッグのヒント
コードが問題を引き起こしているか不明な場合:
# ワークフローを起動する
npx output workflow start my-workflow '{"input": "test"}'
# ワークフロー ID を取得し、デバッグコマンドでリプレイの挙動を確認する
npx output workflow debug <workflowId> --json
トレース結果に非決定性に関するエラーや警告が出ていないか確認してください。
関連する問題
- ワークフローコード内での I/O 処理については
output-error-direct-ioを参照してください - ロジック内でランダム値が必要な場合は、step 内で生成するか、入力として渡してください
原文(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
- Fix the code using solutions above
- Run the workflow:
npx output workflow run <name> '<input>' - Run again with same input: Result should be identical
- 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": "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 による自動翻訳です。