# Gotchas & Best Practices ## Common Errors ### "setState() not syncing" **Cause:** Mutating state directly or not calling `setState()` after modifications **Solution:** Always use `setState()` with immutable updates: ```ts // ❌ this.state.count++ // ✅ this.setState({...this.state, count: this.state.count + 1}) ``` ### "Message history grows unbounded (AIChatAgent)" **Cause:** `this.messages` in `AIChatAgent` accumulates all messages indefinitely **Solution:** Manually trim old messages periodically: ```ts export class ChatAgent extends AIChatAgent { 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 }); } } ``` ### "SQL injection vulnerability" **Cause:** Direct string interpolation in SQL queries **Solution:** Use parameterized queries: ```ts // ❌ this.sql`...WHERE id = '${userId}'` // ✅ this.sql`...WHERE id = ${userId}` ``` ### "WebSocket connection timeout" **Cause:** Not calling `conn.accept()` in `onConnect` **Solution:** Always accept connections: ```ts async onConnect(conn: Connection, ctx: ConnectionContext) { conn.accept(); conn.setState({userId: "123"}); } ``` ### "Schedule limit exceeded" **Cause:** More than 1000 scheduled tasks per agent **Solution:** Clean up old schedules and limit creation rate: ```ts async checkSchedules() { if ((await this.getSchedules()).length > 800) console.warn("Near limit!"); } ``` ### "AI Gateway unavailable" **Cause:** AI service timeout or quota exceeded **Solution:** Add error handling and fallbacks: ```ts try { return await this.env.AI.run(model, {prompt}); } catch (e) { console.error("AI error:", e); return {error: "Unavailable"}; } ``` ### "@callable method returns undefined" **Cause:** Method doesn't return JSON-serializable value, or has non-serializable types **Solution:** Ensure return values are plain objects/arrays/primitives: ```ts // ❌ Returns class instance @callable() async getData() { return new Date(); } // ✅ Returns serializable object @callable() async getData() { return { timestamp: Date.now() }; } ``` ### "Resumable stream not resuming" **Cause:** Stream ID must be deterministic for resumption to work **Solution:** Use AIChatAgent (automatic) or ensure consistent stream IDs: ```ts // AIChatAgent handles this automatically export class ChatAgent extends AIChatAgent { // Resumption works out of the box } ``` ### "MCP connection loss on hibernation" **Cause:** MCP server connections don't survive hibernation **Solution:** Re-register servers in `onStart()` or check connection status: ```ts onStart() { // Re-register MCP servers after hibernation await this.mcp.registerServer("github", { url: env.MCP_URL, auth: {...} }); } ``` ### "Agent not found" **Cause:** Durable Object binding missing or incorrect class name **Solution:** Verify DO binding in wrangler.jsonc and class name matches ## Rate Limits & Quotas | 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 | ## Best Practices ### State Management - Use immutable updates: `setState({...this.state, key: newValue})` - Trim unbounded arrays (messages, logs) periodically - Store large data in SQL, not state ### SQL Usage - Create tables in `onStart()`, not `onRequest()` - Use parameterized queries: `` sql`WHERE id = ${id}` `` (NOT `` sql`WHERE id = '${id}'` ``) - Index frequently queried columns ### Scheduling - Monitor schedule count: `await this.getSchedules()` - Cancel completed tasks to stay under 1000 limit - Use cron strings for recurring tasks ### WebSockets - Always call `conn.accept()` in `onConnect()` - Handle client disconnects gracefully - Broadcast to `this.connections` efficiently ### AI Integration - Use `AIChatAgent` for chat interfaces (auto-streaming, resumption) - Trim message history to avoid token limits - Handle AI errors with try/catch and fallbacks ### Production Deployment - **Rate limiting:** Implement request throttling for high-traffic agents (>1000 req/s) - **Monitoring:** Log critical errors, track schedule count, monitor storage usage - **Graceful degradation:** Handle AI service outages with fallbacks - **Message trimming:** Enforce max history length (e.g., 100 messages) in AIChatAgent - **MCP reliability:** Re-register servers on hibernation, implement retry logic