4.5 KiB
Gotchas & Debugging
Common Errors
"Step Timeout"
Cause: Step execution exceeding 10 minute default timeout or configured timeout
Solution: Set custom timeout with step.do('long operation', {timeout: '30 minutes'}, async () => {...}) or increase CPU limit in wrangler.jsonc (max 5min CPU time)
"waitForEvent Timeout"
Cause: Event not received within timeout period (default 24h, max 365d)
Solution: Wrap in try-catch to handle timeout gracefully and proceed with default behavior
"Non-Deterministic Step Names"
Cause: Using dynamic values like Date.now() in step names causes replay issues
Solution: Use deterministic values like event.instanceId for step names
"State Lost in Variables"
Cause: Using module-level or local variables to store state which is lost on hibernation
Solution: Return values from step.do() which are automatically persisted: const total = await step.do('step 1', async () => 10)
"Non-Deterministic Conditionals"
Cause: Using non-deterministic logic (like Date.now()) outside steps in conditionals
Solution: Move non-deterministic operations inside steps: const isLate = await step.do('check', async () => Date.now() > deadline)
"Large Step Returns Exceeding Limit"
Cause: Returning data >1 MiB from step
Solution: Store large data in R2 and return only reference: { key: 'r2-object-key' }
"Step Exceeded CPU Limit But Ran for < 30s"
Cause: Confusion between CPU time (active compute) and wall-clock time (includes I/O waits)
Solution: Network requests, database queries, and sleeps don't count toward CPU. 30s limit = 30s of active processing
"Idempotency Violation"
Cause: Step operations not idempotent, causing duplicate charges or actions on retry
Solution: Check if operation already completed before executing (e.g., check if customer already charged)
"Instance ID Collision"
Cause: Reusing instance IDs causing conflicts
Solution: Use unique IDs with timestamp: await env.MY_WORKFLOW.create({ id: \${userId}-${Date.now()}`, params: {} })`
"Instance Data Disappeared After Completion"
Cause: Completed/errored instances are automatically deleted after retention period (3 days free / 30 days paid)
Solution: Export critical data to KV/R2/D1 before workflow completes
"Missing await on step.do"
Cause: Forgetting to await step.do() causing fire-and-forget behavior
Solution: Always await step operations: await step.do('task', ...)
Limits
| Limit | Free | Paid | Notes |
|---|---|---|---|
| CPU per step | 10ms | 30s (default), 5min (max) | Set via limits.cpu_ms in wrangler.jsonc |
| Step state | 1 MiB | 1 MiB | Per step return value |
| Instance state | 100 MB | 1 GB | Total state per workflow instance |
| Steps per workflow | 1,024 | 1,024 | step.sleep() doesn't count |
| Executions per day | 100k | Unlimited | Daily execution limit |
| Concurrent instances | 25 | 10k | Maximum concurrent workflows; waiting state excluded |
| Queued instances | 100k | 1M | Maximum queued workflow instances |
| Subrequests per step | 50 | 1,000 | Maximum outbound requests per step |
| State retention | 3 days | 30 days | How long completed instances kept |
| Step timeout default | 10 min | 10 min | Per attempt |
| waitForEvent timeout default | 24h | 24h | Maximum 365 days |
| waitForEvent timeout max | 365 days | 365 days | Maximum wait time |
Note: Instances in waiting state (from step.sleep or step.waitForEvent) don't count toward concurrent instance limit, allowing millions of sleeping workflows.
Pricing
| Metric | Free | Paid | Notes |
|---|---|---|---|
| Requests | 100k/day | 10M/mo + $0.30/M | Workflow invocations |
| CPU time | 10ms/invoke | 30M CPU-ms/mo + $0.02/M CPU-ms | Actual CPU usage |
| Storage | 1 GB | 1 GB/mo + $0.20/GB-mo | All instances (running/errored/sleeping/completed) |
References
See: README.md, configuration.md, api.md, patterns.md