9.8 KiB
Cloudflare Workflows - Common Issues
Last Updated: 2025-10-22
This document details all known issues with Cloudflare Workflows and their solutions.
Issue #1: I/O Context Error
Error Message:
Cannot perform I/O on behalf of a different request
Description: When trying to use I/O objects (like fetch responses, file handles, etc.) created in one request context from a different request's handler, Cloudflare Workers throws this error. This is a fundamental Workers platform limitation.
Root Cause: I/O objects are bound to the request context that created them. Workflows create a new execution context for each step, so I/O must happen within the step's callback.
Prevention:
❌ Bad - I/O outside step:
// This will fail!
const response = await fetch('https://api.example.com/data');
const data = await response.json();
await step.do('use data', async () => {
// Trying to use data from outside step's context
return data; // ❌ Error!
});
✅ 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(); // ✅ Correct
});
Workaround:
Always perform all I/O operations (fetch, KV reads, D1 queries, R2 operations) within step.do() callbacks.
Source: Cloudflare Workers platform limitation
Issue #2: NonRetryableError Behaves Differently in Dev vs Production
Error Message:
(No specific error - workflow retries when it shouldn't)
Description:
When throwing a NonRetryableError with an empty message in development mode (wrangler dev), the workflow incorrectly retries the failed step. In production, it correctly exits without retrying.
Root Cause: Bug in the development environment handling of empty NonRetryableError messages.
Prevention:
❌ Bad - Empty message:
import { NonRetryableError } from 'cloudflare:workflows';
// May retry in dev mode
throw new NonRetryableError();
✅ Good - Always provide message:
import { NonRetryableError } from 'cloudflare:workflows';
// Works consistently in dev and production
throw new NonRetryableError('User not found');
throw new NonRetryableError('Invalid authentication credentials');
throw new NonRetryableError('Amount exceeds limit');
Workaround: Always provide a descriptive message when throwing NonRetryableError.
Source: cloudflare/workers-sdk#10113 Status: Reported July 2025, not yet fixed
Issue #3: WorkflowEvent Export Not Found
Error Message:
The requested module 'cloudflare:workers' does not provide an export named 'WorkflowEvent'
Description:
TypeScript cannot find the WorkflowEvent export from the cloudflare:workers module. This usually happens with outdated type definitions.
Root Cause:
- Outdated
@cloudflare/workers-typespackage - Incorrect import statement
- Missing types in tsconfig.json
Prevention:
✅ Ensure latest types installed:
npm install -D @cloudflare/workers-types@latest
✅ Correct import:
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';
✅ Correct tsconfig.json:
{
"compilerOptions": {
"types": ["@cloudflare/workers-types/2023-07-01"],
"moduleResolution": "bundler"
}
}
Workaround:
- Update workers types:
npm install -D @cloudflare/workers-types@latest - Run type generation:
npx wrangler types - Restart TypeScript server in your editor
Source: Community reports, package versioning issues Latest Working Version: @cloudflare/workers-types@4.20251014.0 (verified 2025-10-22)
Issue #4: Serialization Error - Non-Serializable Return Values
Error Message:
Error: Could not serialize return value
(or workflow hangs without clear error)
Description:
Attempting to return non-serializable values from step.do() or run() methods causes serialization failures. The workflow instance may error or hang.
Root Cause: Workflows persist state between steps by serializing return values. Only JSON-serializable types are supported.
Prevention:
❌ Bad - Non-serializable types:
// ❌ Function
await step.do('bad example', async () => {
return {
data: [1, 2, 3],
transform: (x) => x * 2 // ❌ Function not serializable
};
});
// ❌ Circular reference
await step.do('bad example 2', async () => {
const obj: any = { name: 'test' };
obj.self = obj; // ❌ Circular reference
return obj;
});
// ❌ Symbol
await step.do('bad example 3', async () => {
return {
id: Symbol('unique'), // ❌ Symbol not serializable
data: 'test'
};
});
// ❌ undefined (use null instead)
await step.do('bad example 4', async () => {
return {
value: undefined // ❌ undefined not serializable
};
});
✅ Good - Only serializable types:
await step.do('good example', async () => {
return {
// ✅ Primitives
string: 'value',
number: 123,
boolean: true,
nullValue: null,
// ✅ Arrays
array: [1, 2, 3],
// ✅ Objects
nested: {
data: 'test',
items: [{ id: 1 }, { id: 2 }]
}
};
});
✅ Convert class instances to plain objects:
class User {
constructor(public id: string, public name: string) {}
toJSON() {
return { id: this.id, name: this.name };
}
}
await step.do('serialize class', async () => {
const user = new User('123', 'Alice');
// ✅ Convert to plain object
return user.toJSON(); // { id: '123', name: 'Alice' }
});
Workaround:
- Only return primitives, arrays, and plain objects
- Convert class instances to plain objects before returning
- Use
nullinstead ofundefined - Avoid circular references
Source: Cloudflare Workflows documentation Reference: Workflows Workers API
Issue #5: Testing Workflows in CI Environments
Error Message:
(Tests pass locally but fail in CI)
Description:
Tests that use vitest-pool-workers to test workflows work reliably in local development but fail inconsistently in CI environments (GitHub Actions, GitLab CI, etc.).
Root Cause:
- Timing issues in CI environments
- Resource constraints in CI runners
- Race conditions in test setup/teardown
Prevention:
✅ Increase timeouts in CI:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
testTimeout: 30000, // Increase from default 5000ms
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' },
},
},
},
});
✅ Add retry logic for flaky tests:
describe('Workflow tests', () => {
it.retry(3)('should complete workflow', async () => {
// Test code
});
});
✅ Use proper test isolation:
import { beforeEach, afterEach } from 'vitest';
let instance: WorkflowInstance;
beforeEach(async () => {
instance = await env.MY_WORKFLOW.create({
params: { userId: '123' }
});
});
afterEach(async () => {
if (instance) {
try {
await instance.terminate();
} catch (error) {
// Instance may already be terminated
}
}
});
Workaround:
- Increase test timeouts for CI
- Add retry logic for flaky tests
- Use proper test isolation
- Consider mocking workflows in unit tests, testing real workflows in integration tests
Source: cloudflare/workers-sdk#10600 Status: Ongoing investigation
Additional Troubleshooting Tips
Workflow Instance Stuck in "Running" State
Possible Causes:
- Step is sleeping for long duration
- Step is waiting for event that never arrives
- Step is retrying with long backoff
Solution:
# Check detailed instance status
npx wrangler workflows instances describe my-workflow <instance-id>
# Look for:
# - Sleep state (shows wake time)
# - waitForEvent state (shows event type and timeout)
# - Retry history (shows attempts and delays)
Step Returns Undefined
Cause: Missing return statement in step callback
Solution:
// ❌ Bad - no return
const result = await step.do('get data', async () => {
const data = await fetchData();
// Missing return!
});
console.log(result); // undefined
// ✅ Good - explicit return
const result = await step.do('get data', async () => {
const data = await fetchData();
return data; // ✅ Return the value
});
Payload Too Large Error
Error:
Payload size exceeds limit
Cause: Workflow parameters or step outputs exceed 128 KB
Solution:
// ❌ Bad - large payload
await env.MY_WORKFLOW.create({
params: {
largeData: hugeArray // >128 KB
}
});
// ✅ Good - store in R2/KV, pass reference
const key = `workflow-data/${crypto.randomUUID()}`;
await env.MY_BUCKET.put(key, JSON.stringify(hugeArray));
await env.MY_WORKFLOW.create({
params: {
dataKey: key // Just pass the key
}
});
Getting Help
If you encounter issues not listed here:
- Check Cloudflare Status: https://www.cloudflarestatus.com/
- Search GitHub Issues: https://github.com/cloudflare/workers-sdk/issues
- Cloudflare Discord: https://discord.gg/cloudflaredev
- Cloudflare Community: https://community.cloudflare.com/
- Official Docs: https://developers.cloudflare.com/workflows/
When reporting issues, include:
- Workflow code (sanitized)
- Wrangler configuration
- Error messages and stack traces
- Workflow instance ID
- Steps to reproduce
- Expected vs actual behavior
Last Updated: 2025-10-22 Maintainer: Jeremy Dawes | jeremy@jezweb.net