• 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-direct-io

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

出力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.

ユースケース
  • ワークフローがハングアップする
  • undefined が返される
  • ワークフロー確定性エラーが表示される
  • ワークフロー内で直接HTTP/API呼び出しが実行される
本文(日本語訳)

ワークフロー関数の直接I/O操作を修正する

概要

このスキルは、ワークフロー関数内でI/O操作(HTTPリクエスト、データベースクエリ、ファイル操作)が直接実行される重大なエラーパターンの診断と修正を支援します。これはTemporalの決定論性(処理結果が常に同じになるべき)要件に違反しています。

このスキルを使用する場合

次のような症状が見られる場合:

  • ワークフローが無限に停止したままになる
  • 結果が未定義または空の状態で返される
  • 「ワークフローは決定論的である必要があります」というエラーが出る
  • ネットワーク操作が無音で失敗する
  • 原因不明のタイムアウトが発生する

根本原因

ワークフロー関数は決定論的である必要があります。つまり、ステップの処理を調整するだけで、I/O操作を直接実行してはいけません。HTTPリクエスト、データベースクエリ、その他の外部操作をワークフロー関数内で直接実行すると:

  1. 停止: I/Oが適切に処理されていないため、ワークフローが停止する可能性があります
  2. 決定論性違反: Temporalはワークフローを再実行しますが、I/Oの結果が異なります
  3. 再試行ロジックなし: 直接呼び出しはOutput SDKの再試行メカニズムを回避します
  4. トレース記録なし: 操作がワークフローのトレース(実行記録)に記録されません

よくある症状

ワークフロー内で直接fetchやaxiosを使用

// 間違い: ワークフロー内に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操作を見つける

ワークフローファイル内で共通の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 )
  • 調整ロジック: 条件分岐、ループ(ステップ呼び出しに対する)
  • データ変換: ステップの結果に対する純粋な関数処理
  • 定数: 静的な値と設定

ワークフロー関数に含めてはいけません:

  • HTTPやAPI呼び出し
  • データベース操作
  • ファイルシステム操作
  • 外部サービスへの呼び出し
  • ネットワークやファイルシステムへのアクセスすべて

検証方法

I/O操作をステップに移動した後:

  1. ワークフローを実行: npx output workflow run <name> --input '<input>'
  2. トレースを確認: npx output workflow debug <id> --json
  3. ステップが表示されることを確認: トレース内に移動したI/Oステップが表示されているか確認
  4. エラーがないことを確認: 決定論性エラーや停止が発生していないか確認

I/O操作をステップで実行する利点

  1. 再試行ロジック: 失敗時にステップを再試行できます
  2. トレース記録: I/O操作がワークフローのトレースに表示されます
  3. タイムアウト: ステップごとに個別のタイムアウトを設定できます
  4. 決定論性: 再実行時に記録された結果が使用されます
  5. デバッグ: 何が起きたかを明確に追跡できます

関連トピック

  • 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:

  1. Hangs: The workflow may hang because I/O isn't properly handled
  2. Determinism violations: Temporal replays workflows, and I/O results differ
  3. No retry logic: Direct calls bypass Output SDK's retry mechanisms
  4. 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 { 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;
  }
} );

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:

  1. Run the workflow: npx output workflow run <name> --input '<input>'
  2. Check the trace: npx output workflow debug <id> --json
  3. Verify steps appear: Look for your I/O steps in the trace
  4. Confirm no errors: No determinism warnings or hangs

Benefits of Steps for I/O

  1. Retry logic: Steps can be retried on failure
  2. Tracing: I/O operations appear in workflow traces
  3. Timeouts: Steps can have individual timeouts
  4. Determinism: Replays use recorded results
  5. 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 による自動翻訳です。