Cloudflare Workflows for durable long-running execution. Use for multi-step workflows, retries, state persistence, or encountering NonRetryableError, execution failed errors.
SKILL.md
Cloudflare Workflows
Status: Production Ready ✅ | Last Verified: 2025-12-27 | Version: 3.0.0
CRITICAL: All I/O (fetch, KV, D1, R2) must happen insidestep.do() callbacks!
Reference: See references/workflow-patterns.md for all patterns
Critical Rules
Always Do ✅
✅ Perform all I/O inside step.do() - Required for durability
✅ Use named steps - Makes debugging easier
✅ Return JSON-serializable data from steps - Required for state persistence
✅ Use step.sleep() for delays - Don't use setTimeout()
✅ Handle errors explicitly - Use try/catch in step callbacks
✅ Use NonRetryableError for permanent failures - Stops retries
Workflow Patterns: See references/workflow-patterns.md for:
Sequential workflows
Parallel execution
Event-driven workflows
Scheduled workflows
Human-in-the-loop workflows
Never Do ❌
❌ Never do I/O outside step.do() - Will fail with "I/O context" error
❌ Never use setTimeout() or setInterval() - Use step.sleep() instead
❌ Never return non-serializable data - Functions, Promises, etc. will fail
❌ Never hardcode timeouts - Use workflow config
❌ Never ignore NonRetryableError - Indicates permanent failure
Top 5 Critical Errors
Error #1: I/O Context Error ⚠️
Error:
Cannot perform I/O on behalf of a different request
Load references/wrangler-commands.md for complete CLI reference with all workflow management commands, monitoring workflows, and debugging stuck instances.
State Persistence
Workflows automatically persist state between steps. No manual state management needed:
export class StatefulWorkflow extends WorkflowEntrypoint {
async run(event, step) {
// Step 1 result is automatically persisted
const result1 = await step.do('step 1', async () => {
return { data: 'value' };
});
// Even if workflow crashes here, step 1 won't re-run
await step.sleep('wait', '1 hour');
// Step 2 can use step 1's result (still available after sleep)
await step.do('step 2', async () => {
console.log(result1.data); // 'value' - persisted!
});
}
}
Key Points:
Step results automatically persisted
Completed steps never re-run (even after crash/restart)
State available throughout workflow lifetime
Limits
Resource
Limit
Step CPU Time
30 seconds
Workflow Duration
30 days
Step Payload Size
128 KB
Workflow Payload Size
128 KB
Steps per Workflow
1,000
Concurrent Instances
1,000 per workflow
Event Payload Size
128 KB
Workarounds:
Large data: Store in KV/R2, pass key in step
Long CPU: Break into smaller steps
Many steps: Consider sub-workflows
Pricing
Duration: $0.02 per million GB-s (same as Workers)
Requests: $0.15 per million (workflow creation + step execution)
State Storage: Included (no additional cost)
Sleep: Free (no CPU usage during sleep)
Example Cost (1M workflow runs):
5 steps each = 5M requests = $0.75
10ms per step = 50GB-s = $0.001
Total: ~$0.75 per million workflows
Troubleshooting
"I/O context" error
Solution: Move all I/O into step.do() callbacks → See references/common-issues.md #1
"Serialization error"
Solution: Return only JSON-serializable data from steps → See references/common-issues.md #2
Workflow retries forever
Solution: Throw NonRetryableError for permanent failures → See references/common-issues.md #3
"WorkflowEvent not found"
Solution: Ensure event names match exactly → See references/common-issues.md #4
"Step timeout exceeded"
Solution: Break long computations into smaller steps → See references/common-issues.md #5