Chapter 04 · Cloudflare Deploy
Subchapter 4.3
references/agents-sdk/gotchas.mdMarkdown5 KBView on GitHub
Cause: Mutating state directly or not calling setState() after modifications
Solution: Always use setState() with immutable updates:
// ❌ this.state.count++
// ✅ this.setState({...this.state, count: this.state.count + 1})Cause: this.messages in AIChatAgent accumulates all messages indefinitely
Solution: Manually trim old messages periodically:
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
// Keep only last 50 messages
if (this.messages.length > 50) {
this.messages = this.messages.slice(-50);
}
return this.streamText({ model: openai("gpt-4"), messages: this.messages, onFinish });
}
}Cause: Direct string interpolation in SQL queries Solution: Use parameterized queries:
// ❌ this.sql`...WHERE id = '${userId}'`
// ✅ this.sql`...WHERE id = ${userId}`Cause: Not calling conn.accept() in onConnect
Solution: Always accept connections:
async onConnect(conn: Connection, ctx: ConnectionContext) { conn.accept(); conn.setState({userId: "123"}); }Cause: More than 1000 scheduled tasks per agent Solution: Clean up old schedules and limit creation rate:
async checkSchedules() { if ((await this.getSchedules()).length > 800) console.warn("Near limit!"); }Cause: AI service timeout or quota exceeded
Solution: Add error handling and fallbacks:
try {
return await this.env.AI.run(model, {prompt});
} catch (e) {
console.error("AI error:", e);
return {error: "Unavailable"};
}Cause: Method doesn’t return JSON-serializable value, or has non-serializable types
Solution: Ensure return values are plain objects/arrays/primitives:
// ❌ Returns class instance
@callable()
async getData() { return new Date(); }
// ✅ Returns serializable object
@callable()
async getData() { return { timestamp: Date.now() }; }Cause: Stream ID must be deterministic for resumption to work
Solution: Use AIChatAgent (automatic) or ensure consistent stream IDs:
// AIChatAgent handles this automatically
export class ChatAgent extends AIChatAgent<Env> {
// Resumption works out of the box
}Cause: MCP server connections don’t survive hibernation
Solution: Re-register servers in onStart() or check connection status:
onStart() {
// Re-register MCP servers after hibernation
await this.mcp.registerServer("github", { url: env.MCP_URL, auth: {...} });
}Cause: Durable Object binding missing or incorrect class name
Solution: Verify DO binding in wrangler.jsonc and class name matches
| Resource/Limit | Value | Notes |
|---|---|---|
| CPU per request | 30s (std), 300s (max) | Set in wrangler.jsonc |
| Memory per instance | 128MB | Shared with WebSockets |
| Storage per agent | 10GB | SQLite storage |
| Scheduled tasks | 1000 per agent | Monitor with getSchedules() |
| WebSocket connections | Unlimited | Within memory limits |
| SQL columns | 100 | Per table |
| SQL row size | 2MB | Key + value |
| WebSocket message | 32MiB | Max size |
| DO requests/sec | ~1000 | Per unique DO instance; rate limit if needed |
| AI Gateway (Workers AI) | Model-specific | Check dashboard for limits |
| MCP requests | Depends on server | Implement retry/backoff |
setState({...this.state, key: newValue})onStart(), not onRequest()sql`WHERE id = ${id}` (NOT sql`WHERE id = '${id}'`)await this.getSchedules()conn.accept() in onConnect()this.connections efficientlyAIChatAgent for chat interfaces (auto-streaming, resumption)