claude-skills/

Anthropic公式スキル・プラグインの日本語ディレクトリ

last sync 22h ago
スキルOfficialdevelopment

⚙️output-error-direct-io

プラグイン
outputai

説明

ワークフロー関数内の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 関数内で直接行うと、以下の問題が発生します。

  1. ハング: I/O が適切に処理されないため、ワークフローがハングすることがある
  2. 決定論違反: Temporal はワークフローをリプレイするが、I/O の結果は毎回異なる
  3. リトライロジックなし: 直接呼び出しでは Output SDK のリトライ機構がバイパスされる
  4. トレースなし: 操作がワークフローのトレースに記録されない

症状

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 に移動したら、以下の手順で動作を確認してください。

  1. ワークフローを実行する: npx output workflow run <name> '<input>'
  2. トレースを確認する: npx output workflow debug <id> --json
  3. step が表示されることを確認する: トレース内に I/O 用の step が記録されているか確認する
  4. エラーがないことを確認する: 決定論に関する警告やハングが発生していないか確認する

I/O に step を使う利点

  1. リトライロジック: 失敗時に step を再実行できる
  2. トレース: I/O 操作がワークフローのトレースに記録される
  3. タイムアウト: 各 step に個別のタイムアウトを設定できる
  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 { 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:

  1. Run the workflow: npx output workflow run <name> '<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 による自動翻訳です。