⚙️output-error-direct-io
- プラグイン
- outputai
- ソース
- GitHub で見る ↗
説明
ワークフロー関数内のOutput SDKにおける直接I/Oを修正します。 次のような場合に使用: - ワークフローがハングする - `undefined` が返される - 「workflow must be deterministic」エラーが表示される - ワークフローのコード内で直接HTTP/APIコールが行われている
原文を表示
Fix direct I/O in Output SDK workflow functions. Use when workflow hangs, returns undefined, shows "workflow must be deterministic" errors, or when HTTP/API calls are made directly in workflow code.
ユースケース
- ✓ワークフローがハングするとき
- ✓undefinedが返されるとき
- ✓ワークフローのコード内で直接HTTP/APIコールが行われているとき
本文(日本語訳)
ワークフロー関数内の直接 I/O を修正する
概要
このスキルは、I/O 操作(HTTP 呼び出し、データベースクエリ、ファイル操作)が step 内ではなく workflow 関数内で直接実行されている、という重大なエラーパターンの診断と修正を支援します。 これは Temporal の決定論的実行要件に違反します。
次のような場合に使用
- ワークフローが無期限にハングする
- レスポンスが
undefinedまたは空になる "workflow must be deterministic"エラーが発生する- ネットワーク操作がサイレントに失敗する
- 原因不明のタイムアウトが発生する
根本原因
workflow 関数は 決定論的 でなければなりません。つまり、I/O を直接実行するのではなく、step を orchestrate(調整)するだけにする必要があります。 HTTP 呼び出し、データベースクエリ、その他の外部操作を workflow 関数内で直接行うと、以下の問題が発生します。
- ハング: I/O が適切に処理されないため、ワークフローがハングすることがある
- 決定論違反: Temporal はワークフローをリプレイするが、I/O の結果は毎回異なる
- リトライロジックなし: 直接呼び出しでは Output SDK のリトライ機構がバイパスされる
- トレースなし: 操作がワークフローのトレースに記録されない
症状
workflow 内での直接 fetch / axios
// 誤り: I/O を workflow 内で直接実行
export default workflow( {
fn: async input => {
const response = await fetch( 'https://api.example.com/data' ); // BAD!
const data = await response.json();
return { data };
}
} );
直接データベース呼び出し
// 誤り: データベース I/O を workflow 内で実行
export default workflow( {
fn: async input => {
const user = await db.users.findById( input.userId ); // BAD!
return { user };
}
} );
ファイルシステム操作
// 誤り: ファイル I/O を workflow 内で実行
import fs from 'fs/promises';
export default workflow( {
fn: async input => {
const data = await fs.readFile( input.path, 'utf-8' ); // BAD!
return { data };
}
} );
解決策
すべての I/O 操作を step 関数に移動してください。 step は非決定論的な操作を扱うために設計されています。
修正前(誤り)
export default workflow( {
fn: async input => {
const response = await fetch( 'https://api.example.com/data' );
const data = await response.json();
return { data };
}
} );
修正後(正しい)
import { z, step, workflow } from '@outputai/core';
import { httpClient } from '@outputai/http';
// I/O 操作用の step を作成
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = httpClient( { prefixUrl: 'https://api.example.com' } );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );
// workflow は step を orchestrate するだけ
export default workflow( {
inputSchema: z.object( {} ),
outputSchema: z.object( { data: z.unknown() } ),
fn: async input => {
const result = await fetchData( { endpoint: 'data' } );
return result;
}
} );
完全な例:データベース操作
修正前(誤り)
export default workflow( {
fn: async input => {
const user = await prisma.user.findUnique( {
where: { id: input.userId }
} );
const orders = await prisma.order.findMany( {
where: { userId: input.userId }
} );
return { user, orders };
}
} );
修正後(正しい)
import { z, step, workflow } from '@outputai/core';
import { prisma } from '../lib/db';
export const fetchUser = step( {
name: 'fetchUser',
inputSchema: z.object( { userId: z.string() } ),
outputSchema: z.object( {
user: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ).nullable()
} ),
fn: async input => {
const user = await prisma.user.findUnique( {
where: { id: input.userId }
} );
return { user };
}
} );
export const fetchOrders = step( {
name: 'fetchOrders',
inputSchema: z.object( { userId: z.string() } ),
outputSchema: z.object( {
orders: z.array( z.object( {
id: z.string(),
total: z.number()
} ) )
} ),
fn: async input => {
const orders = await prisma.order.findMany( {
where: { userId: input.userId }
} );
return { orders };
}
} );
export default workflow( {
inputSchema: z.object( { userId: z.string() } ),
outputSchema: z.object( {
user: z.unknown(),
orders: z.array( z.unknown() )
} ),
fn: async input => {
const { user } = await fetchUser( { userId: input.userId } );
const { orders } = await fetchOrders( { userId: input.userId } );
return { user, orders };
}
} );
workflow 内の直接 I/O を見つける
workflow ファイル内で一般的な I/O パターンを検索します。
# fetch 呼び出しを検索
grep -rn "await fetch" src/workflows/
# axios 呼び出しを検索
grep -rn "axios\." src/workflows/
# データベース操作を検索
grep -rn "prisma\.\|db\.\|mongoose\." src/workflows/
# ファイルシステム操作を検索
grep -rn "fs\.\|readFile\|writeFile" src/workflows/
検索結果をひとつずつ確認し、それが workflow 関数内にあるのか step 関数内にあるのかを確認してください。
workflow 関数内に書いてよいもの
workflow 関数に含めるべきもの:
- step 呼び出し:
await myStep( input ) - Orchestration ロジック: 条件分岐、ループ(step 呼び出しを伴うもの)
- データ変換: step の結果に対する純粋関数
- 定数: 静的な値や設定値
workflow 関数に含めてはいけないもの:
- HTTP / API 呼び出し
- データベース操作
- ファイルシステム操作
- 外部サービス呼び出し
- ネットワークやファイルシステムと通信するあらゆる処理
確認方法
I/O を step に移動したら、以下の手順で動作を確認してください。
- ワークフローを実行する:
npx output workflow run <name> '<input>' - トレースを確認する:
npx output workflow debug <id> --json - step が表示されることを確認する: トレース内に I/O 用の step が記録されているか確認する
- エラーがないことを確認する: 決定論に関する警告やハングが発生していないか確認する
I/O に step を使う利点
- リトライロジック: 失敗時に step を再実行できる
- トレース: I/O 操作がワークフローのトレースに記録される
- タイムアウト: 各 step に個別のタイムアウトを設定できる
- 決定論: リプレイ時は記録済みの結果を使用する
- デバッグ: 何が起きたかを明確に把握できる
関連情報
- HTTP クライアントのベストプラクティスについては
output-error-http-clientを参照 - その他の原因による非決定論エラーについては
output-error-nondeterminismを参照
原文(English)を表示
Fix Direct I/O in Workflow Functions
Overview
This skill helps diagnose and fix a critical error pattern where I/O operations (HTTP calls, database queries, file operations) are performed directly in workflow functions instead of in steps. This violates Temporal's determinism requirements.
When to Use This Skill
You're seeing:
- Workflow hangs indefinitely
- Undefined or empty responses
- "workflow must be deterministic" errors
- Network operations failing silently
- Timeouts without clear cause
Root Cause
Workflow functions must be deterministic - they should only orchestrate steps, not perform I/O directly. When you make HTTP calls, database queries, or any external operations directly in a workflow function:
- Hangs: The workflow may hang because I/O isn't properly handled
- Determinism violations: Temporal replays workflows, and I/O results differ
- No retry logic: Direct calls bypass Output SDK's retry mechanisms
- No tracing: Operations aren't recorded in the workflow trace
Symptoms
Direct fetch/axios in Workflow
// WRONG: I/O directly in workflow
export default workflow( {
fn: async input => {
const response = await fetch( 'https://api.example.com/data' ); // BAD!
const data = await response.json();
return { data };
}
} );
Direct Database Calls
// WRONG: Database I/O in workflow
export default workflow( {
fn: async input => {
const user = await db.users.findById( input.userId ); // BAD!
return { user };
}
} );
File System Operations
// WRONG: File I/O in workflow
import fs from 'fs/promises';
export default workflow( {
fn: async input => {
const data = await fs.readFile( input.path, 'utf-8' ); // BAD!
return { data };
}
} );
Solution
Move ALL I/O operations to step functions. Steps are designed to handle non-deterministic operations.
Before (Wrong)
export default workflow( {
fn: async input => {
const response = await fetch( 'https://api.example.com/data' );
const data = await response.json();
return { data };
}
} );
After (Correct)
import { z, step, workflow } from '@outputai/core';
import { httpClient } from '@outputai/http';
// Create a step for the I/O operation
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = httpClient( { prefixUrl: 'https://api.example.com' } );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );
// Workflow only orchestrates steps
export default workflow( {
inputSchema: z.object( {} ),
outputSchema: z.object( { data: z.unknown() } ),
fn: async input => {
const result = await fetchData( { endpoint: 'data' } );
return result;
}
} );
Complete Example: Database Operation
Before (Wrong)
export default workflow( {
fn: async input => {
const user = await prisma.user.findUnique( {
where: { id: input.userId }
} );
const orders = await prisma.order.findMany( {
where: { userId: input.userId }
} );
return { user, orders };
}
} );
After (Correct)
import { z, step, workflow } from '@outputai/core';
import { prisma } from '../lib/db';
export const fetchUser = step( {
name: 'fetchUser',
inputSchema: z.object( { userId: z.string() } ),
outputSchema: z.object( {
user: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ).nullable()
} ),
fn: async input => {
const user = await prisma.user.findUnique( {
where: { id: input.userId }
} );
return { user };
}
} );
export const fetchOrders = step( {
name: 'fetchOrders',
inputSchema: z.object( { userId: z.string() } ),
outputSchema: z.object( {
orders: z.array( z.object( {
id: z.string(),
total: z.number()
} ) )
} ),
fn: async input => {
const orders = await prisma.order.findMany( {
where: { userId: input.userId }
} );
return { orders };
}
} );
export default workflow( {
inputSchema: z.object( { userId: z.string() } ),
outputSchema: z.object( {
user: z.unknown(),
orders: z.array( z.unknown() )
} ),
fn: async input => {
const { user } = await fetchUser( { userId: input.userId } );
const { orders } = await fetchOrders( { userId: input.userId } );
return { user, orders };
}
} );
Finding Direct I/O in Workflows
Search for common I/O patterns in workflow files:
# Find fetch calls
grep -rn "await fetch" src/workflows/
# Find axios calls
grep -rn "axios\." src/workflows/
# Find database operations
grep -rn "prisma\.\|db\.\|mongoose\." src/workflows/
# Find file system operations
grep -rn "fs\.\|readFile\|writeFile" src/workflows/
Then review each match to see if it's in a workflow function vs a step function.
What CAN Be in Workflow Functions
Workflow functions should contain:
- Step calls:
await myStep( input ) - Orchestration logic: conditionals, loops (over step calls)
- Data transformation: Pure functions on step results
- Constants: Static values and configuration
Workflow functions should NOT contain:
- HTTP/API calls
- Database operations
- File system operations
- External service calls
- Anything that talks to the network or filesystem
Verification
After moving I/O to steps:
- Run the workflow:
npx output workflow run <name> '<input>' - Check the trace:
npx output workflow debug <id> --json - Verify steps appear: Look for your I/O steps in the trace
- Confirm no errors: No determinism warnings or hangs
Benefits of Steps for I/O
- Retry logic: Steps can be retried on failure
- Tracing: I/O operations appear in workflow traces
- Timeouts: Steps can have individual timeouts
- Determinism: Replays use recorded results
- Debugging: Clear visibility into what happened
Related Issues
- For HTTP client best practices, see
output-error-http-client - For non-determinism from other causes, see
output-error-nondeterminism
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。