cloudflare-workflows について
cloudflare-workflowsは、Cloudflare Workersで永続的なワークフローを構築するためのスキルです。ステップ実行、リトライ、スリープ、イベント待機、テストと、既知の問題への対処を説明します。
主な機能
- 01リトライとタイムアウトを設定できるステップ
- 02スリープとイベント待機のパターン
- 03Vitestによるテストガイド
- 04再起動時の状態と副作用の安全な扱い
- 05既知のプラットフォーム問題への回避策
活用シーン
- 01リトライ可能なステップでワークフローを実装
- 02ワークフローを一時停止しイベント後に再開
- 03Vitestでワークフローの動作をテスト
- 04再起動後の副作用の重複を防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill cloudflare-workflowsインストール後、Claude Code で/cloudflare-workflowsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.claude-plugin/plugin.jsonreferences/common-issues.mdworkflow-patterns.mdrules/cloudflare-workflows.mdtemplates/basic-workflow.tsscheduled-workflow.tsworker-trigger.tsworkflow-with-events.tsworkflow-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 よりもワークフローのテストに大きな影響を及ぼします。
予防策:
- vitest の設定で
testTimeoutを増やします:export default defineWorkersConfig({ test: { testTimeout: 60_000 // Default: 5000ms } }); - CI のリソース制限(CPU/メモリー)を確認します
- ストレージの分離をテストしない場合は、
isolatedStorage: falseを使用します - 重要なワークフローでは、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 ダッシュボードからアクセスします。
- Workers & Pages
- ワークフローを選択します
- インスタンスとメトリクスを表示します
メトリクスには以下が含まれます。
- 作成されたインスタンスの合計数
- 成功率/エラー率
- 平均実行時間
- ステップレベルのパフォーマンス
- 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 Free | Workers Paid |
|---|---|---|
| ワークフローあたりの最大ステップ数 | 1,024 | 1,024 |
| ステップあたりの最大状態サイズ | 1 MiB | 1 MiB |
| インスタンスあたりの最大状態サイズ | 100 MB | 1 GB |
| イベントペイロードの最大サイズ | 1 MiB | 1 MiB |
| sleep/sleepUntil の最大期間 | 365日 | 365日 |
| waitForEvent の最大タイムアウト | 365日 | 365日 |
| ステップあたりの CPU 時間 | 10 ms | 30 sec(デフォルト)、5 min(最大) |
| ステップあたりの期間(実時間) | 無制限 | 無制限 |
| ワークフローの最大実行数 | 100,000/日 | 無制限 |
| 同時実行インスタンス数 | 25 | 10,000(2025年10月、4,500から増加) |
| インスタンス作成レート | 100/秒 | 100/秒(2025年10月、10倍高速化) |
| キューに入れられるインスタンスの最大数 | 100,000 | 1,000,000 |
| インスタンスあたりのサブリクエストの最大数 | 50/request | 1,000/request |
| 保持期間(完了状態) | 3日 | 30日 |
| Workflow 名の最大文字数 | 64 chars | 64 chars |
| インスタンス ID の最大文字数 | 100 chars | 100 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/
関連ドキュメント
- Cloudflare Workflows ドキュメント: https://developers.cloudflare.com/workflows/
- スタートガイド: https://developers.cloudflare.com/workflows/get-started/guide/
- Workers API: https://developers.cloudflare.com/workflows/build/workers-api/
- Vitest テスト: https://developers.cloudflare.com/workers/testing/vitest-integration/
- スリープと再試行: https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/
- イベントとパラメーター: https://developers.cloudflare.com/workflows/build/events-and-parameters/
- 制限事項: https://developers.cloudflare.com/workflows/reference/limits/
- 料金: https://developers.cloudflare.com/workflows/platform/pricing/
- 変更履歴: https://developers.cloudflare.com/workflows/reference/changelog/
- MCP ツール: 最新のドキュメントには
mcp__cloudflare-docs__search_cloudflare_documentationを使用してください
最終更新日: 2026-01-21 バージョン: 2.0.0 変更内容: 文書化された既知の問題を12件追加しました(TIER 1~2 の調査結果):waitForEvent のタイムアウトバグ、getPlatformProxy の失敗、リダイレクト時のインスタンス消失、Vitest の CI に関する問題、ローカル開発の制限、状態の永続化ルール、キャッシュに関する注意点、冪等性のパターン メンテナー: Jeremy Dawes | [email protected]