出力SDK(ファイル書き込みなど直接的なデータ処理)のワークフロー関数における直接入出力の問題を修正します。 次のような場合に使用: - ワークフローがハングアップする - undefined(値が定義されていない状態)が返される - 「ワークフローは確定的である必要があります」というエラーが表示される - ワークフローコード内で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.
このスキルは、ワークフロー関数内でI/O操作(HTTPリクエスト、データベースクエリ、ファイル操作)が直接実行される重大なエラーパターンの診断と修正を支援します。これはTemporalの決定論性(処理結果が常に同じになるべき)要件に違反しています。
次のような症状が見られる場合:
ワークフロー関数は決定論的である必要があります。つまり、ステップの処理を調整するだけで、I/O操作を直接実行してはいけません。HTTPリクエスト、データベースクエリ、その他の外部操作をワークフロー関数内で直接実行すると:
// 間違い: ワークフロー内にI/Oがある
export default workflow( {
fn: async input => {
const response = await fetch( 'https://api.example.com/data' ); // ダメ!
const data = await response.json();
return { data };
}
} );
// 間違い: ワークフロー内にデータベースI/Oがある
export default workflow( {
fn: async input => {
const user = await db.users.findById( input.userId ); // ダメ!
return { user };
}
} );
// 間違い: ワークフロー内にファイルI/Oがある
import fs from 'fs/promises';
export default workflow( {
fn: async input => {
const data = await fs.readFile( input.path, 'utf-8' ); // ダメ!
return { data };
}
} );
**すべてのI/O操作をステップ関数に移動してください。**ステップは非決定論的な操作を処理するために設計されています。
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 { createKyClient } from '@outputai/http';
// I/O操作用のステップを作成
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( {
endpoint: z.string()
} ),
outputSchema: z.object( {
data: z.unknown()
} ),
fn: async input => {
const client = createKyClient( { prefix: 'https://api.example.com' } );
const data = await client.get( input.endpoint ).json();
return { data };
}
} );
// ワークフローはステップの実行を調整するだけ
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 };
}
} );
ワークフローファイル内で共通の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/
各該当箇所を確認して、ワークフロー関数内にあるのか、ステップ関数内にあるのかを確認してください。
ワークフロー関数に含められます:
await myStep( input )ワークフロー関数に含めてはいけません:
I/O操作をステップに移動した後:
npx output workflow run <name> --input '<input>'npx output workflow debug <id> --jsonoutput-error-http-clientを参照してくださいoutput-error-nondeterminismを参照してください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.
You're seeing:
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:
// 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 };
}
} );
// WRONG: Database I/O in workflow
export default workflow( {
fn: async input => {
const user = await db.users.findById( input.userId ); // BAD!
return { user };
}
} );
// 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 };
}
} );
Move ALL I/O operations to step functions. Steps are designed to handle non-deterministic operations.
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 { createKyClient } 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 = createKyClient( { prefix: '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;
}
} );
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 };
}
} );
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.
Workflow functions should contain:
await myStep( input )Workflow functions should NOT contain:
After moving I/O to steps:
npx output workflow run <name> --input '<input>'npx output workflow debug <id> --jsonoutput-error-http-clientoutput-error-nondeterminism原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。