Token导航 LogoToken导航

Agent Skill · 开发

cloudflare-workflows(Cloudflare 工作流程)

cloudflare-workflows 用于记录任务执行中的错误、用户纠正、经验和能力缺口,适合在 Codex、Claude、Cursor、Gemini CLI 中希望让 Agent 持续沉淀问题、修正和最佳实践时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

cloudflare-workflows 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描有未通过项,按安装量在开发类的 21,746 个 Skill 里排第 4,240。

总安装 8,686周安装 348GitHub Stars 750下载 2,812核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:cloudflare-workflows(Cloudflare 工作流程)
来源仓库:https://github.com/jezweb/claude-skills
仓库路径:skills/cloudflare-workflows
安装命令:
npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-workflows
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

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

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

Cloudflare Workers 上具有自动重试、状态持久性和事件处理功能的持久、长时间运行的工作流程。

支持三种核心步骤类型:step.do() 对于可配置重试的工作执行,step.sleep() 对于延迟,以及step.waitForEvent() 用于带超时的外部事件处理 跨步骤和休眠自动保持可序列化状态;防止 12 个已记录的错误,包括 waitForEvent 超时错误、I/O 上下文违规和非幂等操作陷阱 包括使用内省 API 进行 Vitest 测试、模拟步骤结果和事件模拟;记录本地开发限制(实例生命周期、getPlatformProxy 不兼容) 可扩展至 10,000 个并发实例,创建速率为 100 步/秒;睡眠实例不计入并发性;每月免费 1000 万次后,定价为每百万步执行 0.30 美元

来源仓库内路径:skills/cloudflare-workflows

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-workfl…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

cloudflare-workflows 怎么安装?

在终端执行 npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-workflows。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

cloudflare-workflows 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

cloudflare-workflows 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

cloudflare-workflows 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

cloudflare-workflows 用的人多吗?

公开数据是累计安装 8,686 次,近一周安装 348 次,下载 2,812 次,GitHub 750 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

cloudflare-workflows 安全吗?

第三方扫描有未通过项,风险较高。除非你确认过源码,否则不建议直接安装到常用环境。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
cloudflare-workflows

Cloudflare 工作流程 · 本页

8,686750需要联网MIT未通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Cloudflare Workflows

Status: Production Ready ✅ (GA since April 2025) Last Updated: 2026-01-09 Dependencies: cloudflare-worker-base (for Worker setup) Latest Versions: wrangler@4.58.0, @cloudflare/workers-types@4.20260109.0

Recent Updates (2025):

  • April 2025: Workflows GA release - waitForEvent API, Vitest testing, CPU time metrics, 4,500 concurrent instances
  • October 2025: Instance creation rate 10x faster (100/sec), concurrency increased to 10,000
  • 2025 Limits: Max steps 1,024, state persistence 1MB/step (100MB-1GB per instance), event payloads 1MB, CPU time 5 min max
  • Testing: cloudflare:test module with introspectWorkflowInstance, disableSleeps, mockStepResult, mockEvent modifiers
  • Platform: Waiting instances don't count toward concurrency, retention 3-30 days, subrequests 50-1,000

Quick Start (5 Minutes)

# 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

CRITICAL: Extends WorkflowEntrypoint, implements run() with step methods, bindings in wrangler.jsonc


Known Issues Prevention

This skill prevents 12 documented errors with Cloudflare Workflows.

Issue #1: waitForEvent Skips Events After Timeout in Local Dev

Error: Events sent after a waitForEvent() timeout are ignored in subsequent waitForEvent() calls Environment: Local development (wrangler dev) only - works correctly in production Source: GitHub Issue #11740

Why It Happens: Bug in miniflare that was fixed in production (May 2025) but not ported to local emulator. After a timeout, the event queue becomes corrupted for that instance.

Prevention:

  • Test waitForEvent timeout scenarios in production/staging, not local dev
  • Avoid chaining multiple waitForEvent() calls where timeouts are expected

Example of Bug:

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)

Status: Known bug, fix pending for miniflare.


Issue #2: getPlatformProxy() Fails With Workflow Bindings

Error: MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start Message: Worker's binding refers to service with named entrypoint, but service has no such entrypoint Source: GitHub Issue #9402

Why It Happens: getPlatformProxy() from wrangler package doesn't support Workflow bindings (similar to how it handles Durable Objects). This blocks Next.js integration and local CLI scripts.

Prevention:

  • Option 1: Comment out workflow bindings when using getPlatformProxy()
  • Option 2: Create separate wrangler.cli.jsonc without workflows for CLI scripts
  • Option 3: Access workflow bindings directly via deployed worker, not proxy
// 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' });

Status: Known limitation, fix planned (filter workflows similar to DOs).


Issue #3: Workflow Instance Lost After Immediate Redirect (Local Dev)

Error: Instance ID returned but instance.not_found when queried Environment: Local development (wrangler dev) only - works correctly in production Source: GitHub Issue #10806

Why It Happens: Returning a redirect immediately after workflow.create() causes request to "soft abort" before workflow initialization completes (single-threaded execution in dev).

Prevention: Use ctx.waitUntil() to ensure workflow initialization completes before redirect:

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);
  }
};

Status: Fixed in recent wrangler versions (post-Sept 2025), but workaround still recommended for compatibility.


Issue #4: Vitest Tests Unreliable in CI Environments

Error: [vitest-worker]: Timeout calling "resolveId" Environment: CI/CD pipelines (GitLab, GitHub Actions) - works locally Source: GitHub Issue #10600

Why It Happens: @cloudflare/vitest-pool-workers has resource constraint issues in CI containers, affecting workflow tests more than other worker types.

Prevention:

  1. Increase testTimeout in vitest config: export default defineWorkersConfig({test: {testTimeout: 60_000 // Default: 5000ms}});
  2. Check CI resource limits (CPU/memory)
  3. Use isolatedStorage: false if not testing storage isolation
  4. Consider testing against deployed instances instead of vitest for critical workflows

Status: Known issue, investigating (Internal: WOR-945).


Issue #5: Instance restart() and terminate() Not Implemented in Local Dev

Error: Error: Not implemented yet when calling instance.restart() or instance.terminate() Environment: Local development (wrangler dev) only - works in production Source: GitHub Issue #11312

Why It Happens: Instance management APIs not yet implemented in miniflare. Additionally, instance status shows running even when workflow is sleeping.

Prevention: Test instance lifecycle management (pause/resume/terminate) in production or staging environment until local dev support is added.

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

Status: Known limitation, no timeline for local dev support.


Issue #6: I/O Must Be Inside step.do() Callbacks

Error: "Cannot perform I/O on behalf of a different request" Source: Cloudflare runtime behavior

Why It Happens: Trying to use I/O objects created in one request context from another request handler.

Prevention: Always perform I/O within step.do() callbacks:

// ❌ 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 Behaves Differently in Dev vs Production

Error: NonRetryableError with empty message causes retries in dev mode but works correctly in production Environment: Development-specific bug Source: GitHub Issue #10113

Why It Happens: Empty error messages are handled differently between miniflare and production runtime.

Prevention: Always provide a message to NonRetryableError:

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

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

Status: Known issue, workaround documented.


Issue #8: In-Memory State Lost on Hibernation

Error: Variables declared outside step.do() reset to initial values after sleep/hibernation Source: Cloudflare Workflows Rules

Why It Happens: Workflows hibernate when the engine detects no pending work. All in-memory state is lost during hibernation.

Prevention: Only use state returned from step.do() - everything else is ephemeral:

// ❌ 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: Non-Deterministic Step Names Break Caching

Error: Steps re-run unnecessarily, performance degradation Source: Cloudflare Workflows Rules

Why It Happens: Step names act as cache keys. Using Date.now(), Math.random(), or other non-deterministic values causes new cache keys every run.

Prevention: Use static, deterministic step names:

// ❌ 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: Promise.race/any Outside step.do() Causes Inconsistency

Error: Different promises resolve on restart, inconsistent behavior Source: Cloudflare Workflows Rules

Why It Happens: Non-deterministic operations outside steps run again on restart, potentially with different results.

Prevention: Keep all non-deterministic logic inside 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

Issue #11: Side Effects Repeat on Restart

Error: Duplicate logs, metrics, or operations after workflow restart Source: Cloudflare Workflows Rules

Why It Happens: Code outside step.do() executes multiple times if the workflow restarts mid-execution.

Prevention: Put logging, metrics, and other side effects inside 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)
});

Issue #12: Non-Idempotent Operations Can Repeat

Error: Double charges, duplicate database writes after step timeout Source: Cloudflare Workflows Rules

Why It Happens: Steps retry individually. If an API call succeeds but the step times out before returning, the retry will call the API again.

Prevention: Guard non-idempotent operations with existence checks:

// ❌ 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 Methods

step.do() - Execute Work

step.do<T>(name: string, config?: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>

Parameters:

  • name - Step name (for observability)
  • config (optional) - Retry configuration (retries, timeout, backoff)
  • callback - Async function that does the work

Returns: Value from callback (must be serializable)

Example:

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());
});

CRITICAL - Serialization:

  • ✅ Allowed: string, number, boolean, Array, Object, null
  • ❌ Forbidden: Function, Symbol, circular references, undefined
  • Throws error if return value isn't JSON serializable

step.sleep() - Relative Sleep

step.sleep(name: string, duration: WorkflowDuration): Promise<void>

Parameters:

  • name - Step name
  • duration - Number (ms) or string: "second", "minute", "hour", "day", "week", "month", "year" (plural forms accepted)

Examples:

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

Note: Resuming workflows take priority over new instances. Sleeps don't count toward step limits.


step.sleepUntil() - Sleep to Specific Date

step.sleepUntil(name: string, timestamp: Date | number): Promise<void>

Parameters:

  • name - Step name
  • timestamp - Date object or UNIX timestamp (milliseconds)

Examples:

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() - Wait for External Event (GA April 2025)

step.waitForEvent<T>(name: string, options: { type: string; timeout?: string | number }): Promise<T>

Parameters:

  • name - Step name
  • options.type - Event type to match
  • options.timeout (optional) - Max wait time (default: 24 hours, max: 30 days)

Returns: Event payload sent via instance.sendEvent()

Example:

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');
    }
  }
};

Timeout handling:

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
}

Default: {retries: {limit: 5, delay: 10000, backoff: 'exponential'}, timeout: '10 minutes'}

Backoff Examples:

// 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 } }

Error Handling

NonRetryableError

Force workflow to fail immediately without retrying:

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;
    });
  }
}

When to use NonRetryableError:

  • ✅ Authentication/authorization failures
  • ✅ Invalid input that won't change
  • ✅ Resource doesn't exist (404)
  • ✅ Validation errors
  • ❌ Network failures (should retry)
  • ❌ Rate limits (should retry with backoff)
  • ❌ Temporary service outages (should retry)

Catch Errors to Continue Workflow

Prevent workflow failure by catching optional step errors:

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 */ });
  }
}

Graceful Degradation:

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

Triggering Workflows

Configure binding (wrangler.jsonc):

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

Trigger from Worker:

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

Instance Management:

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();

State Persistence

Workflows automatically persist state returned from step.do():

✅ Serializable:

  • Primitives: string, number, boolean, null
  • Arrays, Objects, Nested structures

❌ Non-Serializable:

  • Functions, Symbols, circular references, undefined, class instances

Example:

// ✅ 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!

Access State Across Steps:

const userData = await step.do('fetch user', async () => ({ id: 123, email: 'user@example.com' }));
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}` }));

Observability

Built-in Metrics (Enhanced in 2025)

Workflows automatically track:

  • Instance status: queued, running, complete, errored, paused, waiting
  • Step execution: start/end times, duration, success/failure
  • Retry history: attempts, errors, delays
  • Sleep state: when workflow will wake up
  • Output: return values from steps and run()
  • CPU time (GA April 2025): Active processing time per instance for billing insights

View Metrics in Dashboard

Access via Cloudflare dashboard:

  1. Workers & Pages
  2. Select your workflow
  3. View instances and metrics

Metrics include:

  • Total instances created
  • Success/error rates
  • Average execution time
  • Step-level performance
  • CPU time consumption (2025 feature)

Programmatic Access

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 Time Configuration (2025):

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

Limits (Updated 2025)

FeatureWorkers FreeWorkers Paid
Max steps per workflow1,0241,024
Max state per step1 MiB1 MiB
Max state per instance100 MB1 GB
Max event payload size1 MiB1 MiB
Max sleep/sleepUntil duration365 days365 days
Max waitForEvent timeout365 days365 days
CPU time per step10 ms30 sec (default), 5 min (max)
Duration (wall clock) per stepUnlimitedUnlimited
Max workflow executions100,000/dayUnlimited
Concurrent instances2510,000 (Oct 2025, up from 4,500)
Instance creation rate100/second100/second (Oct 2025, 10x faster)
Max queued instances100,0001,000,000
Max subrequests per instance50/request1,000/request
Retention (completed state)3 days30 days
Max Workflow name length64 chars64 chars
Max instance ID length100 chars100 chars

CRITICAL Notes:

  • step.sleep() and step.sleepUntil() do NOT count toward 1,024 step limit
  • Waiting instances (sleeping, retrying, or waiting for events) do NOT count toward concurrency limits
  • Instance creation rate increased 10x (October 2025): 100 per 10 seconds → 100 per second
  • Max concurrency increased (October 2025): 4,500 → 10,000 concurrent instances
  • State persistence limits increased (2025): 128 KB → 1 MiB per step, 100 MB - 1 GB per instance
  • Event payload size increased (2025): 128 KB → 1 MiB
  • CPU time configurable via wrangler.jsonc: {"limits": {"cpu_ms": 300000}} (5 min max)

Pricing

Requires Workers Paid plan ($5/month)

Workflow Executions:

  • First 10,000,000 step executions/month: FREE
  • After that: $0.30 per million step executions

What counts as a step execution:

  • Each step.do() call
  • Each retry of a step
  • step.sleep(), step.sleepUntil(), step.waitForEvent() do NOT count

Cost examples:

  • Workflow with 5 steps, no retries: 5 step executions
  • Workflow with 3 steps, 1 step retries 2 times: 5 step executions (3 + 2)
  • 10M simple workflows/month (5 steps each): ((50M - 10M) / 1M) × $0.30 = $12/month

Vitest Testing (GA April 2025)

Workflows support full testing integration via cloudflare:test module.

Setup

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' } } } } } } });

Introspection 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
  }
});

Test Modifiers

  • disableSleeps(steps?) - Skip sleeps instantly
  • mockStepResult(step, result) - Mock step.do() result
  • mockStepError(step, error, times?) - Force step.do() to throw
  • mockEvent(event) - Send mock event to step.waitForEvent()
  • forceStepTimeout(step, times?) - Force step.do() timeout
  • forceEventTimeout(step) - Force step.waitForEvent() timeout

Official Docs: https://developers.cloudflare.com/workers/testing/vitest-integration/


Related Documentation


Last Updated: 2026-01-21 Version: 2.0.0 Changes: Added 12 documented Known Issues (TIER 1-2 research findings): waitForEvent timeout bug, getPlatformProxy failure, redirect instance loss, Vitest CI issues, local dev limitations, state persistence rules, caching gotchas, and idempotency patterns Maintainer: Jeremy Dawes | jeremy@jezweb.net

平台分布

Claude Code

28.14%
按下载量换算791

Gemini CLI

24.91%
按下载量换算700

Antigravity

16.04%
按下载量换算451

Cursor

12.93%
按下载量换算364

OpenCode

8.59%
按下载量换算242

Codex

3.89%
按下载量换算109

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills