Interactive Durable Objects debugging workflow. Diagnoses common DO errors, configuration issues, and runtime problems with step-by-step troubleshooting.
Diagnoses and fixes Durable Objects errors through interactive troubleshooting for configuration, runtime, and performance issues.
/plugin marketplace add secondsky/claude-skills/plugin install cloudflare-durable-objects@claude-skillsInteractive command to diagnose and fix common Durable Objects issues. Provides step-by-step troubleshooting for configuration errors, runtime failures, and performance problems.
This command helps debug DO issues by:
Use AskUserQuestion tool to understand the problem:
Question: "What type of issue are you experiencing with Durable Objects?" Header: "Issue Type" Options:
If user selected error-based category (Deployment, Runtime):
Prompt: "Please paste the full error message:"
Enter error message (or 'none' if no specific error):
_____________________________________________
_____________________________________________
_____________________________________________
Parse error message for known patterns:
Deployment Error Patterns:
"class_name" not found → Class export missingmigrations required → Missing migrationbinding not found → Binding misconfigurationmodule not found → Import path issueRuntime Error Patterns:
constructor failed → Constructor timeout/errorSQL error → Query syntax or schema issueWebSocket → Hibernation or connection issuealarm → Alarm handler errorstorage limit exceeded → 1GB/128MB limit reachedQuestion: "When does this issue occur?" Header: "Timing" Options:
Question: "Have you made any recent changes to your Durable Objects configuration or code?" Header: "Recent Changes" Options (multiselect):
Based on error category and context, run relevant diagnostic checks:
Run validation script:
./scripts/validate-do-config.sh
Parse output for errors:
Common Configuration Issues:
Missing Class Export
Error: Class 'MyDO' not found in src/index.ts
Fix:
// src/index.ts
export class MyDO extends DurableObject { ... }
// Or if in separate file:
export { MyDO } from "./MyDO";
Missing Migration
Error: No migrations found in configuration
Fix:
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyDO"]
}
]
Binding Name Mismatch
Error: Binding 'MY_DO' references class 'MyDO' but class not in migrations
Fix: Add class to migrations or update binding to match existing class.
Read DO class implementation and check for common issues:
# Find DO constructor
grep -A 20 "constructor(ctx: DurableObjectState" src/*.ts
Common Constructor Issues:
Long-Running Constructor
// ❌ BAD: Heavy work in constructor (blocks all requests)
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
await this.heavyInitialization(); // Blocks!
}
Fix:
// ✅ GOOD: Use blockConcurrencyWhile for critical initialization only
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.ctx.blockConcurrencyWhile(async () => {
// Only critical schema setup here
await this.ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS ...`);
});
}
Missing super() Call
// ❌ BAD: Forgot super()
constructor(ctx: DurableObjectState, env: Env) {
this.ctx = ctx; // Error!
}
Fix:
// ✅ GOOD: Always call super() first
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
}
# Find SQL queries
grep -r "storage.sql.exec" src/
Common Storage Issues:
SQL Syntax Errors
// ❌ BAD: Invalid SQL
await this.ctx.storage.sql.exec("SELECT * FORM users");
// ^^^^ typo!
Fix: Check SQL syntax, use SQLite documentation
Transaction Handling
// ❌ BAD: Nested transactions not supported
await this.ctx.storage.sql.exec("BEGIN TRANSACTION");
await this.ctx.storage.sql.exec("BEGIN TRANSACTION"); // Error!
Fix: Use single transaction, avoid nesting
Storage Limit Exceeded
// ❌ Exceeds 1GB SQL limit or 128MB KV limit
Fix: Implement archiving pattern (see templates/ttl-cleanup-do.ts)
# Check WebSocket implementation
grep -r "acceptWebSocket\|webSocketMessage\|webSocketClose" src/
Common WebSocket Issues:
setTimeout/setInterval in DO
// ❌ BAD: Breaks hibernation!
webSocketMessage(ws: WebSocket, message: string) {
setTimeout(() => { ... }, 5000); // Never hibernates!
}
Fix:
// ✅ GOOD: Use alarms for delayed execution
webSocketMessage(ws: WebSocket, message: string) {
await this.ctx.storage.setAlarm(Date.now() + 5000);
}
Outgoing WebSocket Connections
// ❌ BAD: Outgoing WebSockets don't hibernate
const ws = new WebSocket("wss://external.com");
Fix: Only server-side (accepted) WebSockets hibernate
Missing Hibernation Tags
// ⚠️ Without tags, all messages wake DO
this.ctx.acceptWebSocket(ws); // No tags
Fix:
// ✅ With tags, filter messages efficiently
this.ctx.acceptWebSocket(ws, ["chat", "presence"]);
# Check alarm implementation
grep -r "async alarm()\|setAlarm\|getAlarm" src/
Common Alarm Issues:
Alarm Handler Throws Error
async alarm() {
throw new Error("boom"); // Retries forever!
}
Fix:
async alarm() {
try {
// Do work
} catch (error) {
console.error("Alarm failed:", error);
// Reschedule or give up
await this.ctx.storage.setAlarm(Date.now() + 60000);
// Don't throw (or retry will loop forever)
}
}
Not Idempotent
async alarm() {
await this.ctx.storage.sql.exec(
"INSERT INTO logs (message) VALUES ('alarm fired')" // Fails on retry!
);
}
Fix:
async alarm() {
// Use UPSERT or check before inserting
await this.ctx.storage.sql.exec(
`INSERT INTO logs (id, message) VALUES (1, 'alarm fired')
ON CONFLICT(id) DO UPDATE SET message = excluded.message`
);
}
Use wrangler tail to observe DO behavior in real-time:
wrangler tail
What to Look For:
Error Logs
Performance Metrics
DO Lifecycle Events
Common Patterns:
Based on diagnostic results, categorize the issue:
Symptoms:
wrangler deploy shows validation errorsRoot Causes:
Solution Path: Fix configuration → Validate → Deploy
Symptoms:
Root Causes:
Solution Path: Fix code → Test locally → Deploy
Symptoms:
Root Causes:
Solution Path: Remove hibernation blockers → Test → Monitor
Symptoms:
Root Causes:
Solution Path: Fix storage logic → Implement TTL/archiving → Test
Symptoms:
Root Causes:
Solution Path: Optimize queries → Add indexes → Profile
Based on identified root cause, provide actionable fix:
🔍 Diagnosis: [ISSUE_NAME]
Root Cause:
[Explanation of what's causing the issue]
Evidence:
- [Diagnostic finding 1]
- [Diagnostic finding 2]
Fix:
[Step-by-step instructions]
Code Changes Required:
[Specific code snippets to add/modify]
Testing:
1. [How to test the fix locally]
2. [How to verify in production]
Deploy:
wrangler deploy
Monitor:
wrangler tail [--filter ...]
Verification:
[How to confirm the issue is resolved]
🔍 Diagnosis: Missing Durable Objects Migration
Root Cause:
Your Durable Object class 'MyDO' is defined and exported,
but not registered in the migrations array. Cloudflare requires
all DO classes to have a migration entry.
Evidence:
- Class exported: ✅ export class MyDO extends DurableObject
- Binding configured: ✅ "MY_DO" → "MyDO"
- Migration entry: ❌ Not found in migrations array
Fix:
1. Open wrangler.jsonc
2. Add migration to migrations array:
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyDO"]
}
]
}
Or use the migration generator:
./scripts/migration-generator.sh --new MyDO
3. Validate configuration:
./scripts/validate-do-config.sh
4. Deploy:
wrangler deploy
Testing:
After deployment, test DO creation:
curl https://your-worker.workers.dev?id=test
Verification:
- Deployment succeeds without migration error
- DO instances can be created
- No "migration required" errors in logs
🔍 Diagnosis: WebSocket Hibernation Blocked by setTimeout
Root Cause:
Your DO uses setTimeout() which prevents WebSocket hibernation.
Durable Objects can only hibernate when NO timers or pending
I/O operations exist.
Evidence:
- Found setTimeout() in webSocketMessage handler (line 45)
- WebSocket connections never show as hibernated in logs
- High memory usage (DO never releases resources)
Fix:
1. Replace setTimeout with Alarms API
❌ BEFORE:
```typescript
webSocketMessage(ws: WebSocket, message: string) {
setTimeout(async () => {
ws.send("delayed message");
}, 5000);
}
✅ AFTER:
webSocketMessage(ws: WebSocket, message: string) {
// Store message metadata for alarm
await this.ctx.storage.put("pendingMessage", {
wsId: ws.serialize(), // Serialize WebSocket state
message: "delayed message",
timestamp: Date.now()
});
// Schedule alarm
await this.ctx.storage.setAlarm(Date.now() + 5000);
}
async alarm() {
const pending = await this.ctx.storage.get("pendingMessage");
if (!pending) return;
// Get WebSocket from serialized state
const websockets = this.ctx.getWebSockets();
const ws = websockets.find(w => w.serializeAttachment() === pending.wsId);
if (ws) {
ws.send(pending.message);
}
await this.ctx.storage.delete("pendingMessage");
// Don't reschedule (one-time alarm)
}
Remove all other setTimeout/setInterval usages
Test locally: wrangler dev
Deploy: wrangler deploy
Monitor hibernation: wrangler tail
Verification:
Documentation:
## Step 5: Test Fix Locally
Guide user through local testing:
### Start Local Development
```bash
wrangler dev
# Create/access DO instance
curl "http://localhost:8787?id=test-123"
Watch console output for:
Based on issue type:
For Configuration Issues:
For Runtime Issues:
For Performance Issues:
For WebSocket Issues:
For Alarm Issues:
Guide production deployment:
wrangler deploy
wrangler tail
Filter for specific patterns:
# Filter by DO name
wrangler tail --filter "MyDO"
# Filter by error level
wrangler tail --filter "error"
# Filter by WebSocket events
wrangler tail --filter "websocket"
Create checklist based on issue:
For All Issues:
For WebSocket Issues:
For Alarm Issues:
For Storage Issues:
Offer follow-up assistance based on issue resolution:
✅ Issue Resolved!
Summary:
- Issue: [ISSUE_NAME]
- Root Cause: [CAUSE]
- Fix Applied: [FIX_DESCRIPTION]
Verification Complete:
✅ Local testing passed
✅ Deployment successful
✅ Production monitoring shows no errors
Recommendations:
- Monitor for [X] hours to ensure stability
- Add tests to prevent regression (see templates/vitest-do-test.ts)
- Document the fix for team reference
Related Resources:
- Load references/best-practices.md for production patterns
- Load references/monitoring-debugging.md for debugging techniques
⚠️ Issue Not Fully Resolved
Status:
- Diagnostic complete
- Fix attempted
- Issue still occurring
Next Steps:
1. Gather more information:
- Enable verbose logging
- Capture full stack traces
- Record exact reproduction steps
2. Check advanced scenarios:
- Load references/top-errors.md for rare issues
- Load references/performance-optimization.md
- Review Cloudflare status page
3. Consider alternative solutions:
- [Alternative approach 1]
- [Alternative approach 2]
4. Escalate if needed:
- Cloudflare Community: community.cloudflare.com
- Cloudflare Discord: discord.gg/cloudflaredev
- Support ticket: dash.cloudflare.com (for paid plans)
Debug Data to Include:
- wrangler.jsonc (sanitized)
- Error messages (full text)
- Wrangler tail logs
- Reproduction steps
- Worker code (relevant sections)
Quick reference for common error patterns:
| Error Message | Root Cause | Fix |
|---|---|---|
"class_name" not found | Class not exported | Add export class ... |
migrations required | Missing migration | Add to migrations array |
binding not found | Binding misconfigured | Fix binding in wrangler.jsonc |
module not found | Import path wrong | Fix import statement |
validation error | Invalid wrangler.jsonc | Check JSON syntax |
| Error Message | Root Cause | Fix |
|---|---|---|
constructor failed | Constructor timeout/error | Reduce constructor work |
SQL error: syntax | Invalid SQL query | Fix SQL syntax |
storage limit exceeded | >1GB SQL or >128MB KV | Implement TTL cleanup |
WebSocket error | Hibernation blocked | Remove setTimeout |
alarm retry | Alarm handler throws | Add try/catch |
| Symptom | Root Cause | Fix |
|---|---|---|
| Slow queries | Missing indexes | Add SQL indexes |
| High latency | Blocking constructor | Move work to request handler |
| Timeout | Large data transfer | Paginate results |
| Memory growth | No cleanup | Implement TTL pattern |
After debugging, recommend:
/do-setup for starting fresh/do-migrate for migration issuestemplates/vitest-do-test.tsreferences/best-practices.mdreferences/monitoring-debugging.mdDebugging is successful when: