DEV Community

Cover image for Debugging Cloudflare Workers with VS Code: Queue Handlers Edition (Part 4)
teaganga
teaganga

Posted on

Debugging Cloudflare Workers with VS Code: Queue Handlers Edition (Part 4)

In Part 3, I showed you how to test queues, cron, and HTTP handlers locally. But here's the question I kept asking: can I actually debug this stuff with breakpoints?

Short answer: Yes, absolutely. And it works better than I expected.

Let me show you how to set it up.


The Setup: VS Code + Wrangler Dev + Debugger

When you run wrangler dev, it starts a Node.js process that runs your Worker. VS Code's debugger can attach to that process, which means you can:

  • ✅ Set breakpoints in your queue() handler
  • ✅ Set breakpoints in your scheduled() handler
  • ✅ Set breakpoints in your fetch() handler
  • ✅ Inspect variables, step through code, everything

Here's how to set it up.


Step 1: Run Wrangler in Debug Mode

Start Wrangler with the inspect flag:

wrangler dev --remote=false --inspect
Enter fullscreen mode Exit fullscreen mode

Important flags:

  • --remote=false - runs locally (not in Cloudflare's dev environment)
  • --inspect - enables the Node.js debugger on port 9229

You should see output like:

Debugger listening on ws://127.0.0.1:9229/...
For help, see: https://nodejs.org/en/docs/inspector
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
Enter fullscreen mode Exit fullscreen mode

That Debugger listening line means you're good to go.


Step 2: Configure VS Code

Create or update .vscode/launch.json in your project:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to Wrangler",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "skipFiles": ["<node_internals>/**"],
      "sourceMaps": true,
      "resolveSourceMapLocations": [
        "${workspaceFolder}/**",
        "!**/node_modules/**"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Attaches VS Code's debugger to the Wrangler process
  • Skips Node.js internal files (you don't want to debug Node itself)
  • Enables source maps so breakpoints work with TypeScript

Step 3: Start Debugging

  1. Run Wrangler in a terminal:
   wrangler dev --remote=false --inspect
Enter fullscreen mode Exit fullscreen mode
  1. Open VS Code's Debug panel (Cmd+Shift+D on Mac, Ctrl+Shift+D on Windows)

  2. Select "Attach to Wrangler" from the dropdown

  3. Press the green play button (or press F5)

You should see "Debugger attached" in your terminal, and VS Code's debug toolbar appears.


Step 4: Set Breakpoints and Trigger Queue Messages

Now the magic happens. Let's debug a queue handler:

// src/index.ts
export default {
  async queue(batch, env, ctx) {
    console.log(`[queue] Received ${batch.messages.length} messages`);

    for (const message of batch.messages) {
      // 👈 Set a breakpoint on this line
      const { type, data } = message.body;

      console.log(`[queue] Processing ${type}`);

      // 👈 And another one here
      await processJob(env, data);

      message.ack();
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Set breakpoints:

  1. Click in the gutter (left of line numbers) in VS Code
  2. A red dot appears - that's your breakpoint

Trigger a queue message:

wrangler queues send my-jobs '{"type": "test", "data": "hello"}'
Enter fullscreen mode Exit fullscreen mode

🎉 Your breakpoint hits!

VS Code pauses execution, and you can:

  • Inspect message.body
  • Check the value of env bindings
  • Step through the code line by line
  • See the call stack

This is incredibly useful for debugging complex queue logic.


Debugging All Three Handler Types

HTTP Handlers (fetch)

Set a breakpoint in your fetch handler:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // 👈 Breakpoint here
    if (url.pathname === '/api/trigger') {
      await env.JOB_QUEUE.send({ type: 'manual' });
      return new Response('Queued');
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Then hit the endpoint:

curl http://localhost:8787/api/trigger
Enter fullscreen mode Exit fullscreen mode

The debugger pauses, and you can inspect the request object, headers, etc.

Scheduled Handlers (scheduled)

Set a breakpoint in your scheduled handler:

export default {
  async scheduled(event, env, ctx) {
    // 👈 Breakpoint here
    const time = new Date(event.scheduledTime);
    console.log('[scheduled] Running at', time);

    await env.JOB_QUEUE.send({ type: 'cron' });
  }
};
Enter fullscreen mode Exit fullscreen mode

Trigger it by pressing s in the Wrangler terminal (or use the /__scheduled endpoint).

The debugger pauses, and you can see the event.scheduledTime value.

Queue Handlers (queue)

We already covered this above, but here's a more complex example:

export default {
  async queue(batch, env, ctx) {
    for (const message of batch.messages) {
      try {
        // 👈 Breakpoint here to inspect message structure
        const job = parseJobMessage(message.body);

        // 👈 Another breakpoint to step through processing
        await processJob(env, job);

        // 👈 Breakpoint to verify ack is called
        message.ack();

      } catch (error) {
        // 👈 Breakpoint to debug failures
        console.error('Job failed:', error);
        message.retry();
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Send messages:

wrangler queues send my-jobs '{"id": 1}'
wrangler queues send my-jobs '{"id": 2}'
Enter fullscreen mode Exit fullscreen mode

You can step through each message in the batch and see exactly what's happening.


Advanced Debugging Techniques

1. Conditional Breakpoints

Right-click a breakpoint in VS Code → "Edit Breakpoint" → "Expression"

message.body.id === 42
Enter fullscreen mode Exit fullscreen mode

Now the breakpoint only hits for messages with id: 42. Super useful when debugging batches.

2. Logpoints

Don't want to stop execution? Use a logpoint instead:

Right-click → "Add Logpoint"

Message {message.id} has data: {message.body}
Enter fullscreen mode Exit fullscreen mode

This logs without pausing, like console.log but without modifying your code.

3. Watch Expressions

In VS Code's Debug panel, add Watch expressions:

message.attempts
env.DATABASE_URL
batch.messages.length
Enter fullscreen mode Exit fullscreen mode

These update automatically as you step through code.

4. Debug Console

While paused, you can run code in the Debug Console:

// Check if a binding exists
> env.MY_QUEUE
Queue { ... }

// Inspect message body
> JSON.stringify(message.body, null, 2)
{
  "type": "test",
  "data": "hello"
}

// Test a function
> await parseJobMessage(message.body)
{ ... }
Enter fullscreen mode Exit fullscreen mode

This is amazing for exploring objects and testing fixes without restarting.


Common Issues and Solutions

Issue 1: Breakpoints Show as Gray/Unverified

Problem: Source maps aren't working correctly.

Solution: Make sure your tsconfig.json has source maps enabled:

{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSourceMap": false
  }
}
Enter fullscreen mode Exit fullscreen mode

And restart both Wrangler and the VS Code debugger.

Issue 2: "Cannot connect to runtime process"

Problem: Wrangler isn't running with --inspect.

Solution: Make sure you started Wrangler with:

wrangler dev --remote=false --inspect
Enter fullscreen mode Exit fullscreen mode

Issue 3: Debugger Disconnects After First Request

Problem: This can happen with certain Wrangler versions.

Solution: Use --persist-to to keep the Worker alive:

wrangler dev --remote=false --inspect --persist-to=.wrangler/state
Enter fullscreen mode Exit fullscreen mode

Issue 4: Can't Debug External Dependencies

Problem: Breakpoints in node_modules don't work.

Solution: Remove this line from launch.json:

"skipFiles": ["<node_internals>/**"]
Enter fullscreen mode Exit fullscreen mode

But be warned - you'll hit a LOT of Node.js internal breakpoints.


My Debugging Workflow

Here's how I debug a typical queue handler issue:

1. Reproduce the Issue Locally

# Terminal 1: Start Wrangler
wrangler dev --remote=false --inspect

# Terminal 2: Send problematic message
wrangler queues send my-jobs '{"id": 123, "type": "problematic"}'
Enter fullscreen mode Exit fullscreen mode

2. Attach Debugger

  • Open VS Code
  • Press F5 to attach
  • Set breakpoints in the queue handler

3. Re-trigger

# Send the same message again
wrangler queues send my-jobs '{"id": 123, "type": "problematic"}'
Enter fullscreen mode Exit fullscreen mode

Debugger hits, and I can step through to find the issue.

4. Test the Fix

Fix the code, save (Wrangler hot-reloads), trigger again:

wrangler queues send my-jobs '{"id": 123, "type": "problematic"}'
Enter fullscreen mode Exit fullscreen mode

If it works, remove breakpoints and move on.


Pro Tips

Use Multiple Launch Configurations

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to Wrangler (Local)",
      "type": "node",
      "request": "attach",
      "port": 9229
    },
    {
      "name": "Attach to Wrangler (Remote)",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "localRoot": "${workspaceFolder}",
      "remoteRoot": "/tmp/worker"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Switch between them based on whether you're using --remote=false or not.

Create a Debug Script

Add to package.json:

{
  "scripts": {
    "dev": "wrangler dev",
    "dev:debug": "wrangler dev --remote=false --inspect",
    "test:queue": "wrangler queues send my-jobs '{\"type\": \"test\"}'"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you can just run:

npm run dev:debug
Enter fullscreen mode Exit fullscreen mode

Use Debugger Statements

Sometimes I just add debugger; directly in the code:

async queue(batch, env, ctx) {
  for (const message of batch.messages) {
    if (message.body.id === 42) {
      debugger; // Automatic breakpoint when debugger is attached
    }

    await processJob(message.body);
    message.ack();
  }
}
Enter fullscreen mode Exit fullscreen mode

This is faster than clicking around in VS Code when you know exactly where to break.


Debugging Production Issues Locally

Sometimes you get a failed message in production and want to replay it locally:

1. Get the Message from Your DLQ

# List messages in your dead letter queue
wrangler queues messages list my-jobs-dlq

# Copy the message body
Enter fullscreen mode Exit fullscreen mode

2. Replay it Locally

# Paste the exact production message
wrangler queues send my-jobs '{"id": 123, "data": "..."}'
Enter fullscreen mode Exit fullscreen mode

3. Debug With Real Data

Now you're debugging with the actual message that failed, which makes finding bugs way easier.


Performance Debugging

You can also profile your queue handlers:

export default {
  async queue(batch, env, ctx) {
    const start = performance.now();

    for (const message of batch.messages) {
      const jobStart = performance.now();

      await processJob(message.body);

      const jobTime = performance.now() - jobStart;
      console.log(`Job ${message.id} took ${jobTime}ms`);

      message.ack();
    }

    const totalTime = performance.now() - start;
    console.log(`Batch of ${batch.messages.length} took ${totalTime}ms`);
  }
};
Enter fullscreen mode Exit fullscreen mode

Set a breakpoint after the loop and inspect totalTime to see if you're hitting performance issues.


Wrapping Up

Yes, you can absolutely debug queue handlers with VS Code.

In fact, you can debug all three handler types (HTTP, cron, queue) with full breakpoint support, variable inspection, and step-through debugging.

The setup is:

  1. Run wrangler dev --remote=false --inspect
  2. Configure VS Code's launch.json to attach on port 9229
  3. Set breakpoints and trigger your handlers
  4. Debug like any other Node.js app

This has completely changed how I develop Workers. I rarely use console.log debugging anymore because breakpoints are so much faster.


Questions? Drop them in the comments! What's your debugging workflow like?


Quick Reference

# Start Wrangler in debug mode
wrangler dev --remote=false --inspect

# Send test queue message
wrangler queues send my-jobs '{"test": true}'

# Trigger scheduled event
# Press 's' in Wrangler terminal
Enter fullscreen mode Exit fullscreen mode
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to Wrangler",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Happy debugging!

Top comments (0)