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 がクラッシュや再起動後に状態を復旧するため、ワークフロー履歴を再度実行する必要があるためです。
非決定論的な操作(実行のたびに異なる値を生成する処理)は、この再実行メカニズムを破壊します。
問題: 乱数は実行のたびに異なります。
// ❌ 間違い: 非決定論的
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 } );
}
} );
問題: タイムスタンプ(時刻情報)は実行のたびに変わります。
// ❌ 間違い: 非決定論的
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() } )
} );
問題: 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() };
}
} );
問題: 動的インポート(実行時にモジュールを読み込む)は異なる結果になる可能性があります。
// ❌ 間違い: 非決定論的なインポートタイミング
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 );
}
}
} );
問題: 環境変数は再実行時に異なる可能性があります。
// ❌ 間違い: 環境が変わる可能性がある
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 関数を特に見てください。非決定論的なコードが問題になるのはワークフロー関数内だけで、ステップ関数内では問題になりません。
npx output workflow run <名前> --input '<入力値>'ワークフロー関数は決定論的である必要があります:
ステップ関数は非決定論的でも構いません:
コードが問題を起こしているか不明な場合:
# ワークフローを実行
npx output workflow start my-workflow --input '{"input": "test"}'
# ワークフロー ID を取得し、デバッグを実行して再実行の動作を確認
npx output workflow debug <workflowId> --json
トレース(実行ログ)で非決定論的な実行に関するエラーや警告を確認してください。
output-error-direct-io を参照してください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 '<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 '{"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.
output-error-direct-io原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。