mcpField
C
cloudflare-workflows

cloudflare-workflows

github.com/jezwebクラウド・DevOps1.1k341 回インストール3 週間前に更新
リポジトリ

cloudflare-workflows について

cloudflare-workflowsは、Cloudflare Workersで永続的なワークフローを構築するためのスキルです。ステップ実行、リトライ、スリープ、イベント待機、テストと、既知の問題への対処を説明します。

主な機能

  1. 01リトライとタイムアウトを設定できるステップ
  2. 02スリープとイベント待機のパターン
  3. 03Vitestによるテストガイド
  4. 04再起動時の状態と副作用の安全な扱い
  5. 05既知のプラットフォーム問題への回避策

活用シーン

  1. 01リトライ可能なステップでワークフローを実装
  2. 02ワークフローを一時停止しイベント後に再開
  3. 03Vitestでワークフローの動作をテスト
  4. 04再起動後の副作用の重複を防止

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-workflows

インストール後、Claude Code で/cloudflare-workflowsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.claude-plugin/
plugin.json
references/
common-issues.md
workflow-patterns.md
rules/
cloudflare-workflows.md
templates/
basic-workflow.ts
scheduled-workflow.ts
worker-trigger.ts
workflow-with-events.ts
workflow-with-retries.ts

詳細

Cloudflare Workflows

ステータス:本番環境対応 ✅(2025年4月から GA) 最終更新日:2026-01-09 依存関係:cloudflare-worker-base(Worker のセットアップ用) 最新バージョン:[email protected]、@cloudflare/[email protected]

最近の更新(2025年):

  • 2025年4月:Workflows の GA リリース — waitForEvent API、Vitest によるテスト、CPU 時間メトリクス、同時実行インスタンス数 4,500
  • 2025年10月:インスタンス作成レートが 10 倍に高速化(100/秒)、同時実行数が 10,000 に増加
  • 2025年の制限:ステップ数の上限 1,024、状態の永続化は 1 ステップあたり 1MB(インスタンスあたり 100MB~1GB)、イベントペイロードは 1MB、CPU 時間の上限は 5 分
  • テスト:introspectWorkflowInstance、disableSleeps、mockStepResult、mockEvent モディファイアを備えた cloudflare:test モジュール
  • プラットフォーム:待機中のインスタンスは同時実行数に含まれず、保持期間は 3~30 日、サブリクエスト数は 50~1,000

クイックスタート(5 分)

# 1. Scaffold project
npm create cloudflare@latest my-workflow -- --template cloudflare/workflows-starter --git --deploy false
cd my-workflow

# 2. Configure wrangler.jsonc
{
  "name": "my-workflow",
  "main": "src/index.ts",
  "compatibility_date": "2025-11-25",
  "workflows": [{
    "name": "my-workflow",
    "binding": "MY_WORKFLOW",
    "class_name": "MyWorkflow"
  }]
}

# 3. Create workflow (src/index.ts)
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const result = await step.do('process', async () => { /* work */ });
    await step.sleep('wait', '1 hour');
    await step.do('continue', async () => { /* more work */ });
  }
}

# 4. Deploy and test
npm run deploy
npx wrangler workflows instances list my-workflow

重要:WorkflowEntrypoint を継承し、step メソッドを使って run() を実装し、バインディングを wrangler.jsonc に記述します。


既知の問題の防止

このスキルは、Cloudflare Workflows に関する、文書化された 12 件のエラーを防止します。

問題 #1:ローカル開発では、タイムアウト後に waitForEvent がイベントをスキップする

エラー:waitForEvent() のタイムアウト後に送信されたイベントが、後続の waitForEvent() 呼び出しで無視されます 環境:ローカル開発(wrangler dev)のみ。本番環境では正常に動作します 出典:GitHub の Issue #11740

発生する理由:本番環境では修正済み(2025年5月)ですが、ローカルエミュレーターには反映されていない miniflare のバグです。タイムアウト後、そのインスタンスのイベントキューが破損します。

防止策:

  • waitForEvent のタイムアウトシナリオは、ローカル開発環境ではなく本番環境/ステージング環境でテストしてください
  • タイムアウトが予想される場合、複数の waitForEvent() 呼び出しを連続して行うのは避けてください

バグの例:

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    for (let i = 0; i < 3; i++) {
      try {
        const evt = await step.waitForEvent(`wait-${i}`, {
          type: 'user-action',
          timeout: '5 seconds'
        });
        console.log(`Iteration ${i}: Received event`);
      } catch {
        console.log(`Iteration ${i}: Timeout`);
      }
    }
  }
}
// In wrangler dev:
// - Iteration 1: ✅ receives event
// - Iteration 2: ⏱️ times out (expected)
// - Iteration 3: ❌ does not receive event (BUG - event is sent but ignored)

ステータス:既知のバグです。miniflare 向けの修正は保留中です。


問題 #2:getPlatformProxy() が Workflow バインディングで失敗する

エラー:MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start メッセージ:Worker のバインディングが名前付きエントリーポイントを持つサービスを参照していますが、そのサービスに該当するエントリーポイントがありません 出典:GitHub の Issue #9402

発生する理由:wrangler パッケージの getPlatformProxy() は、Workflow バインディングをサポートしていません(Durable Objects の処理と同様です)。このため、Next.js との統合やローカル CLI スクリプトが妨げられます。

防止策:

  • 方法 1:getPlatformProxy() を使用する際は、workflow バインディングをコメントアウトします
  • 方法 2:CLI スクリプト用に、workflows を含まない別の wrangler.cli.jsonc を作成します
  • 方法 3:プロキシではなく、デプロイ済みの worker を介して workflow バインディングに直接アクセスします
// Workaround: Separate config for CLI scripts
// wrangler.cli.jsonc (no workflows)
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-20"
  // workflows commented out
}

// Use in script:
import { getPlatformProxy } from 'wrangler';
const { env } = await getPlatformProxy({ configPath: './wrangler.cli.jsonc' });

ステータス:既知の制限です。修正が予定されています(DO と同様に workflows をフィルタリングします)。


問題 #3:即座にリダイレクトすると Workflow インスタンスが失われる(ローカル開発)

エラー:インスタンス ID が返されますが、照会すると instance.not_found になります 環境:ローカル開発(wrangler dev)のみ。本番環境では正常に動作します 出典:GitHub の Issue #10806

発生する理由:workflow.create() の直後にリダイレクトを返すと、Workflow の初期化が完了する前にリクエストが「ソフトアボート」されます(開発環境ではシングルスレッドで実行されます)。

防止策:ctx.waitUntil() を使って、リダイレクトの前に Workflow の初期化が完了するようにします。

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const workflow = await env.MY_WORKFLOW.create({ params: { userId: '123' } });

    // ✅ Ensure workflow initialization completes
    ctx.waitUntil(workflow.status());

    return Response.redirect('/dashboard', 302);
  }
};

ステータス:最近の wrangler バージョン(2025年9月以降)で修正済みですが、互換性のため、引き続き回避策を推奨します。

Issue #4:Vitest のテストが CI 環境で不安定

エラー:[vitest-worker]: Timeout calling "resolveId" 環境:CI/CD パイプライン(GitLab、GitHub Actions)— ローカルでは動作 ソース:GitHub Issue #10600

発生する理由:@cloudflare/vitest-pool-workers は CI コンテナーでリソース制約の問題があり、ほかの種類の Worker よりもワークフローのテストに大きな影響を及ぼします。

予防策:

  1. vitest の設定で testTimeout を増やします:
    export default defineWorkersConfig({
      test: {
        testTimeout: 60_000 // Default: 5000ms
      }
    });
    
  2. CI のリソース制限(CPU/メモリー)を確認します
  3. ストレージの分離をテストしない場合は、isolatedStorage: false を使用します
  4. 重要なワークフローでは、vitest ではなくデプロイ済みインスタンスに対するテストを検討します

ステータス:既知の問題です。調査中です(内部:WOR-945)。


Issue #5:ローカル開発では Instance の restart() と terminate() が未実装

エラー:instance.restart() または instance.terminate() の呼び出し時に Error: Not implemented yet 環境:ローカル開発(wrangler dev)のみ — 本番環境では動作 ソース:GitHub Issue #11312

発生する理由:インスタンス管理 API は miniflare でまだ実装されていません。また、ワークフローがスリープ中でも、インスタンスのステータスは running と表示されます。

予防策:ローカル開発のサポートが追加されるまでは、本番環境またはステージング環境でインスタンスのライフサイクル管理(一時停止/再開/終了)をテストします。

const instance = await env.MY_WORKFLOW.get(instanceId);

// ❌ Fails in wrangler dev
await instance.restart();    // Error: Not implemented yet
await instance.terminate();  // Error: Not implemented yet

// ✅ Works in production

ステータス:既知の制限事項です。ローカル開発のサポート時期は未定です。


Issue #6:I/O は step.do() コールバック内で実行する必要がある

エラー:"Cannot perform I/O on behalf of a different request" ソース:Cloudflare のランタイム動作

発生する理由:あるリクエストコンテキストで作成された I/O オブジェクトを、別のリクエストハンドラーから使用しようとしています。

予防策:I/O は必ず step.do() コールバック内で実行します:

// ❌ Bad - I/O outside step
const response = await fetch('https://api.example.com/data');
const data = await response.json();

await step.do('use data', async () => {
  return data;  // This will fail!
});

// ✅ Good - I/O inside step
const data = await step.do('fetch data', async () => {
  const response = await fetch('https://api.example.com/data');
  return await response.json();
});

Issue #7:NonRetryableError の動作が開発環境と本番環境で異なる

エラー:メッセージが空の NonRetryableError は、開発モードではリトライされますが、本番環境では正しく動作します 環境:開発環境固有のバグ ソース:GitHub Issue #10113

発生する理由:空のエラーメッセージは、miniflare と本番ランタイムで異なる方法で処理されます。

予防策:NonRetryableError には必ずメッセージを指定します:

// ❌ Retries in dev, exits in prod
throw new NonRetryableError('');

// ✅ Exits in both environments
throw new NonRetryableError('Validation failed');

ステータス:既知の問題です。回避策がドキュメントに記載されています。


Issue #8:休止状態への移行時にインメモリ状態が失われる

エラー:step.do() の外で宣言された変数は、スリープ/休止状態への移行後に初期値にリセットされます ソース:Cloudflare Workflows Rules

発生する理由:エンジンが保留中の処理がないと判断すると、ワークフローは休止状態になります。休止状態への移行中に、すべてのインメモリ状態が失われます。

予防策:step.do() から返された状態のみを使用します。それ以外はすべて一時的なものです:

// ❌ BAD - In-memory variable lost on hibernation
let counter = 0;
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    counter = await step.do('increment', async () => counter + 1);
    await step.sleep('wait', '1 hour'); // ← Hibernates here, in-memory state lost
    console.log(counter); // ❌ Will be 0, not 1!
  }
}

// ✅ GOOD - State from step.do() return values persists
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const counter = await step.do('increment', async () => 1);
    await step.sleep('wait', '1 hour');
    console.log(counter); // ✅ Still 1
  }
}

Issue #9:非決定的なステップ名によってキャッシュが機能しなくなる

エラー:ステップが不必要に再実行され、パフォーマンスが低下します ソース:Cloudflare Workflows Rules

発生する理由:ステップ名はキャッシュキーとして機能します。Date.now()、Math.random()、その他の非決定的な値を使用すると、実行ごとに新しいキャッシュキーが生成されます。

予防策:静的で決定的なステップ名を使用します:

// ❌ BAD - Non-deterministic step name
await step.do(`fetch-data-${Date.now()}`, async () => {
  return await fetchExpensiveData();
});
// Every execution creates new cache key → step always re-runs

// ✅ GOOD - Deterministic step name
await step.do('fetch-data', async () => {
  return await fetchExpensiveData();
});
// Same cache key → result reused on restart/retry

Issue #10:step.do() の外で Promise.race/any を使うと一貫性が失われる

エラー:再起動時に解決される Promise が異なり、動作に一貫性がなくなります ソース:Cloudflare Workflows Rules

発生する理由:ステップの外にある非決定的な処理は、再起動時に再実行され、結果が異なる可能性があります。

予防策:非決定的なロジックはすべて step.do() 内に記述します:

// ❌ BAD - Race outside step
const fastest = await Promise.race([fetchA(), fetchB()]);
await step.do('use result', async () => fastest);
// On restart: race runs again, different promise might win

// ✅ GOOD - Race inside step
const fastest = await step.do('fetch fastest', async () => {
  return await Promise.race([fetchA(), fetchB()]);
});
// On restart: cached result used, consistent behavior

イシュー #11:再起動時に副作用が繰り返される

エラー:ワークフローの再起動後にログ、メトリクス、または操作が重複する 出典:Cloudflare Workflows Rules

発生する理由:ワークフローの実行中に再起動すると、step.do() の外部にあるコードが複数回実行されます。

予防策:ログ記録、メトリクス、その他の副作用は step.do() の内部に記述してください。

// ❌ BAD - Side effect outside step
console.log('Workflow started'); // ← Logs multiple times on restart
await step.do('work', async () => { /* work */ });

// ✅ GOOD - Side effects inside step
await step.do('log start', async () => {
  console.log('Workflow started'); // ← Logs once (cached)
});

イシュー #12:冪等でない操作が繰り返される可能性がある

エラー:ステップのタイムアウト後に二重請求やデータベースへの書き込みが重複する 出典:Cloudflare Workflows Rules

発生する理由:各ステップは個別に再試行されます。API 呼び出しが成功しても、ステップが戻り値を返す前にタイムアウトすると、再試行時に API が再び呼び出されます。

予防策:冪等でない操作は、存在確認でガードしてください。

// ❌ BAD - Charge customer without check
await step.do('charge', async () => {
  return await stripe.charges.create({ amount: 1000, customer: customerId });
});
// If step times out after charge succeeds, retry charges AGAIN!

// ✅ GOOD - Check for existing charge first
await step.do('charge', async () => {
  const existing = await stripe.charges.list({ customer: customerId, limit: 1 });
  if (existing.data.length > 0) return existing.data[0]; // Idempotent
  return await stripe.charges.create({ amount: 1000, customer: customerId });
});

ステップメソッド

step.do() - 作業を実行
step.do<T>(name: string, config?: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>

パラメーター:

  • name - ステップ名(可観測性のため)
  • config(任意)- 再試行の設定(再試行回数、タイムアウト、バックオフ)
  • callback - 作業を実行する非同期関数

戻り値: コールバックからの値(シリアライズ可能である必要があります)

例:

const result = await step.do('call API', { retries: { limit: 10, delay: '10s', backoff: 'exponential' }, timeout: '5 min' }, async () => {
  return await fetch('https://api.example.com/data').then(r => r.json());
});

重要 - シリアライズ:

  • ✅ 使用可能:string、number、boolean、Array、Object、null
  • ❌ 使用不可:Function、Symbol、循環参照、undefined
  • 戻り値が JSON としてシリアライズできない場合はエラーが発生します

step.sleep() - 相対時間でスリープ
step.sleep(name: string, duration: WorkflowDuration): Promise<void>

パラメーター:

  • name - ステップ名
  • duration - 数値(ミリ秒)または文字列:"second"、"minute"、"hour"、"day"、"week"、"month"、"year"(複数形も使用できます)

例:

await step.sleep('wait 5 minutes', '5 minutes');
await step.sleep('wait 1 hour', '1 hour');
await step.sleep('wait 2 days', '2 days');
await step.sleep('wait 30 seconds', 30000);  // milliseconds

注: 再開中のワークフローは新しいインスタンスより優先されます。スリープはステップ数の上限にカウントされません。


step.sleepUntil() - 指定した日時までスリープ
step.sleepUntil(name: string, timestamp: Date | number): Promise<void>

パラメーター:

  • name - ステップ名
  • timestamp - Date オブジェクトまたは UNIX タイムスタンプ(ミリ秒)

例:

await step.sleepUntil('wait for launch', new Date('2025-12-25T00:00:00Z'));
await step.sleepUntil('wait until time', Date.parse('24 Oct 2024 13:00:00 UTC'));

step.waitForEvent() - 外部イベントを待機(2025年4月 GA)
step.waitForEvent<T>(name: string, options: { type: string; timeout?: string | number }): Promise<T>

パラメーター:

  • name - ステップ名
  • options.type - 一致させるイベントタイプ
  • options.timeout(任意)- 最大待機時間(デフォルト:24時間、最大:30日)

戻り値: instance.sendEvent() 経由で送信されたイベントペイロード

例:

export class PaymentWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('create payment', async () => { /* Stripe API */ });

    const webhookData = await step.waitForEvent<StripeWebhook>(
      'wait for payment confirmation',
      { type: 'stripe-webhook', timeout: '1 hour' }
    );

    if (webhookData.status === 'succeeded') {
      await step.do('fulfill order', async () => { /* fulfill */ });
    }
  }
}

// Worker sends event to workflow
export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    if (req.url.includes('/webhook/stripe')) {
      const instance = await env.PAYMENT_WORKFLOW.get(instanceId);
      await instance.sendEvent({ type: 'stripe-webhook', payload: await req.json() });
      return new Response('OK');
    }
  }
};

タイムアウト処理:

try {
  const event = await step.waitForEvent('wait for user', { type: 'user-submitted', timeout: '10 minutes' });
} catch (error) {
  await step.do('send reminder', async () => { /* reminder */ });
}

WorkflowStepConfig

interface WorkflowStepConfig {
  retries?: {
    limit: number;          // Max attempts (Infinity allowed)
    delay: string | number; // Delay between retries
    backoff?: 'constant' | 'linear' | 'exponential';
  };
  timeout?: string | number; // Max time per attempt
}

デフォルト: { retries: { limit: 5, delay: 10000, backoff: 'exponential' }, timeout: '10 minutes' }

バックオフの例:

// Constant: 30s, 30s, 30s
{ retries: { limit: 3, delay: '30 seconds', backoff: 'constant' } }

// Linear: 1m, 2m, 3m, 4m, 5m
{ retries: { limit: 5, delay: '1 minute', backoff: 'linear' } }

// Exponential (recommended): 10s, 20s, 40s, 80s, 160s
{ retries: { limit: 10, delay: '10 seconds', backoff: 'exponential' }, timeout: '5 minutes' }

// Unlimited retries
{ retries: { limit: Infinity, delay: '1 minute', backoff: 'exponential' } }

// No retries
{ retries: { limit: 0 } }

エラー処理

NonRetryableError

再試行せずにワークフローを直ちに失敗させます。

import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('validate input', async () => {
      if (!event.payload.userId) {
        throw new NonRetryableError('userId is required');
      }

      // Validate user exists
      const user = await this.env.DB.prepare(
        'SELECT * FROM users WHERE id = ?'
      ).bind(event.payload.userId).first();

      if (!user) {
        // Terminal error - retrying won't help
        throw new NonRetryableError('User not found');
      }

      return user;
    });
  }
}

NonRetryableError を使用する場面:

  • ✅ 認証/認可エラー
  • ✅ 変更されることのない無効な入力
  • ✅ リソースが存在しない(404)
  • ✅ 検証エラー
  • ❌ ネットワーク障害(再試行する必要があります)
  • ❌ レート制限(バックオフして再試行する必要があります)
  • ❌ 一時的なサービス停止(再試行する必要があります)

エラーを捕捉してワークフローを続行

任意のステップのエラーを捕捉し、ワークフローの失敗を防ぎます。

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('process payment', async () => { /* critical */ });

    try {
      await step.do('send email', async () => { /* optional */ });
    } catch (error) {
      await step.do('log failure', async () => {
        await this.env.DB.prepare('INSERT INTO failed_emails VALUES (?, ?)').bind(event.payload.userId, error.message).run();
      });
    }

    await step.do('update status', async () => { /* continues */ });
  }
}

グレースフルデグラデーション:

let result;
try {
  result = await step.do('call primary API', async () => await callPrimaryAPI());
} catch {
  result = await step.do('call backup API', async () => await callBackupAPI());
}

ワークフローのトリガー

バインディングを設定(wrangler.jsonc):

{
  "workflows": [{
    "name": "my-workflow",
    "binding": "MY_WORKFLOW",
    "class_name": "MyWorkflow",
    "script_name": "workflow-worker"  // If workflow in different Worker
  }]
}

Worker からトリガー:

const instance = await env.MY_WORKFLOW.create({ params: { userId: '123' } });
return Response.json({ id: instance.id, status: await instance.status() });

インスタンス管理:

const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();  // { status: 'running'|'complete'|'errored'|'queued', error, output }
await instance.sendEvent({ type: 'user-action', payload: { action: 'approved' } });
await instance.pause();
await instance.resume();
await instance.terminate();

状態の永続化

ワークフローは step.do() から返された状態を自動的に永続化します。

✅ シリアライズ可能:

  • プリミティブ:string、number、boolean、null
  • 配列、オブジェクト、ネストされた構造

❌ シリアライズ不可:

  • 関数、シンボル、循環参照、undefined、クラスインスタンス

例:

// ✅ Good
const result = await step.do('fetch data', async () => ({
  users: [{ id: 1, name: 'Alice' }],
  timestamp: Date.now(),
  metadata: null
}));

// ❌ Bad - function not serializable
const bad = await step.do('bad', async () => ({ data: [1, 2, 3], transform: (x) => x * 2 }));  // Throws error!

ステップ間で状態にアクセス:

const userData = await step.do('fetch user', async () => ({ id: 123, email: '[email protected]' }));
const orderData = await step.do('create order', async () => ({ userId: userData.id, orderId: 'ORD-456' }));
await step.do('send email', async () => sendEmail({ to: userData.email, subject: `Order ${orderData.orderId}` }));

可観測性

組み込みメトリクス(2025年に強化)

ワークフローでは以下が自動的に追跡されます。

  • インスタンスのステータス:キュー内、実行中、完了、エラー、一時停止、待機中
  • ステップの実行:開始/終了時刻、所要時間、成功/失敗
  • 再試行履歴:試行回数、エラー、遅延
  • スリープ状態:ワークフローが再開する時刻
  • 出力:ステップおよび run() の戻り値
  • CPU 時間(2025年4月に GA):請求に関する分析に役立つ、インスタンスごとのアクティブな処理時間
ダッシュボードでメトリクスを表示する

Cloudflare ダッシュボードからアクセスします。

  1. Workers & Pages
  2. ワークフローを選択します
  3. インスタンスとメトリクスを表示します

メトリクスには以下が含まれます。

  • 作成されたインスタンスの合計数
  • 成功率/エラー率
  • 平均実行時間
  • ステップレベルのパフォーマンス
  • CPU 時間の消費量(2025年の機能)
プログラムからのアクセス
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();

console.log(status);
// {
//   status: 'complete' | 'running' | 'errored' | 'queued' | 'waiting' | 'unknown',
//   error: string | null,
//   output: { userId: '123', status: 'processed' }
// }

CPU 時間の設定(2025年):

// wrangler.jsonc
{ "limits": { "cpu_ms": 300000 } }  // 5 minutes max (default: 30 seconds)

制限事項(2025年更新)

機能Workers FreeWorkers Paid
ワークフローあたりの最大ステップ数1,0241,024
ステップあたりの最大状態サイズ1 MiB1 MiB
インスタンスあたりの最大状態サイズ100 MB1 GB
イベントペイロードの最大サイズ1 MiB1 MiB
sleep/sleepUntil の最大期間365日365日
waitForEvent の最大タイムアウト365日365日
ステップあたりの CPU 時間10 ms30 sec(デフォルト)、5 min(最大)
ステップあたりの期間(実時間)無制限無制限
ワークフローの最大実行数100,000/日無制限
同時実行インスタンス数2510,000(2025年10月、4,500から増加)
インスタンス作成レート100/秒100/秒(2025年10月、10倍高速化)
キューに入れられるインスタンスの最大数100,0001,000,000
インスタンスあたりのサブリクエストの最大数50/request1,000/request
保持期間(完了状態)3日30日
Workflow 名の最大文字数64 chars64 chars
インスタンス ID の最大文字数100 chars100 chars

重要な注意事項:

  • step.sleep() と step.sleepUntil() は、1,024 ステップの上限にカウントされません
  • 待機中のインスタンス(スリープ中、再試行中、またはイベント待ち)は、同時実行数の上限にカウントされません
  • インスタンス作成レートが10倍に増加(2025年10月):10秒あたり100件 → 1秒あたり100件
  • 同時実行数の上限が増加(2025年10月):同時実行インスタンス数4,500件 → 10,000件
  • 状態の永続化に関する制限が拡大(2025年):ステップあたり128 KB → 1 MiB、インスタンスあたり100 MB~1 GB
  • イベントペイロードのサイズ上限が拡大(2025年):128 KB → 1 MiB
  • wrangler.jsonc で CPU 時間を設定できます:{ "limits": { "cpu_ms": 300000 } }(最大5分)

料金

Workers Paid プランが必要です(月額$5)

ワークフローの実行:

  • 月間最初の10,000,000ステップ実行:無料
  • それ以降:ステップ実行100万回あたり$0.30

ステップ実行としてカウントされるもの:

  • 各 step.do() 呼び出し
  • ステップの再試行ごと
  • step.sleep()、step.sleepUntil()、step.waitForEvent() はカウントされません

料金の例:

  • 5ステップで再試行なしのワークフロー:5ステップ実行
  • 3ステップのワークフローで、1つのステップを2回再試行:5ステップ実行(3 + 2)
  • 月間1,000万件のシンプルなワークフロー(各5ステップ):((50M - 10M) / 1M) × $0.30 = 月額$12

Vitest によるテスト(2025年4月に GA)

ワークフローは cloudflare:test モジュールを通じて、テストとの完全な統合をサポートします。

セットアップ
npm install -D vitest@latest @cloudflare/vitest-pool-workers@latest

vitest.config.ts:

import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({ test: { poolOptions: { workers: { miniflare: { bindings: { MY_WORKFLOW: { scriptName: 'workflow' } } } } } } });
イントロスペクション API
import { env, introspectWorkflowInstance } from 'cloudflare:test';

it('should complete workflow', async () => {
  const instance = await introspectWorkflowInstance(env.MY_WORKFLOW, 'test-123');

  try {
    await instance.modify(async (m) => {
      await m.disableSleeps();  // Skip all sleeps
      await m.mockStepResult({ name: 'fetch data' }, { users: [{ id: 1 }] });  // Mock step result
      await m.mockEvent({ type: 'approval', payload: { approved: true } });  // Send mock event
      await m.mockStepError({ name: 'call API' }, new Error('Network timeout'), 1);  // Force error once
    });

    await env.MY_WORKFLOW.create({ id: 'test-123' });
    await expect(instance.waitForStatus('complete')).resolves.not.toThrow();
  } finally {
    await instance.dispose();  // Cleanup
  }
});
テスト用修飾子
  • disableSleeps(steps?) - スリープを即座にスキップします
  • mockStepResult(step, result) - step.do() の結果をモックします
  • mockStepError(step, error, times?) - step.do() がエラーをスローするようにします
  • mockEvent(event) - step.waitForEvent() にモックイベントを送信します
  • forceStepTimeout(step, times?) - step.do() がタイムアウトするようにします
  • forceEventTimeout(step) - step.waitForEvent() がタイムアウトするようにします

公式ドキュメント:https://developers.cloudflare.com/workers/testing/vitest-integration/


関連ドキュメント


最終更新日: 2026-01-21 バージョン: 2.0.0 変更内容: 文書化された既知の問題を12件追加しました(TIER 1~2 の調査結果):waitForEvent のタイムアウトバグ、getPlatformProxy の失敗、リダイレクト時のインスタンス消失、Vitest の CI に関する問題、ローカル開発の制限、状態の永続化ルール、キャッシュに関する注意点、冪等性のパターン メンテナー: Jeremy Dawes | [email protected]

コメント