Loading episodes…
0:00 0:00

Build Debug Tools Your AI Can See

00:00
BACK TO HOME

Build Debug Tools Your AI Can See

10xTeam May 22, 2026 8 min read

tags: [debugging, cdp, chrome-devtools-protocol, browser-automation, ai-visibility, tooling, collaboration] related:

  • 018_write_capable_mcp.md
  • 024_test_suite_doesnt_test_browser.md supersedes: ~ status: current —

030 — Build Debug Tools Your AI Can See


Every debug tool you build has an implicit audience. Console logs: you, watching a terminal. Network tab: you, watching DevTools. A debug overlay panel: you, looking at a screen.

None of these audiences include the AI you’re working with.

When you’re debugging browser state alongside an AI — push subscription status, service worker registration, localStorage flags, permissions — and the relevant information lives in a panel only you can see, one of three things happens:

You relay manually. The AI asks, you look, you describe what you see, the AI responds. This works but it’s slow, lossy, and breaks the collaborative rhythm. Every relay is a context switch for you and a trust-the-human’s-description moment for the AI.

The AI guesses. Without visibility into runtime state, it reasons from code alone. “The code looks correct” is not the same as “the subscription is actually stored.” The AI reaches a confident wrong conclusion because it can see the implementation but not the execution.

You build a workaround — a REST endpoint that mirrors browser state, a bridge server, a WebSocket relay. All of these solve the problem. None of them are the right starting point.

The right starting point is Chrome Remote Debugging Protocol. It’s already there.


The Visibility Gap in Practice

Consider a push notification subscription that isn’t reaching a backend. The subscription flow is entirely browser-side: service worker registration, PushManager.subscribe(), a fire-and-forget fetch to a server. You’ve built a debug panel that reads all of this state beautifully — subscription status, SW scope, endpoint URL, localStorage flags.

The AI working with you on this bug needs to see that state. It can’t.

So it asks you to check. What’s the push permission? Is the SW active? Is the dismissed flag set? You look, you report back. The AI suggests a fix. You apply it and look again.

This is collaborative debugging with a relay bottleneck in the middle. The relay is you.

The relay exists not because the information is unavailable — your debug panel has it — but because the wire between the panel and the AI doesn’t exist yet.


What CDP Gives You

Chrome Remote Debugging Protocol is not a third-party tool. It’s built into every Chromium-based browser, has been for years, and is the same protocol that powers Chrome DevTools, Lighthouse, Playwright, Puppeteer, and every headless testing framework you’ve used.

To enable it, Chrome needs one flag at launch:

google-chrome \
  --remote-debugging-port=9222 \
  --profile-directory=Default \
  --restore-last-session

Your profile, cookies, and sessions are preserved. And now Chrome speaks JSON over HTTP and WebSocket on port 9222.

From that point:

# List open tabs
curl http://localhost:9222/json

# Connect to a tab via its webSocketDebuggerUrl
# Send Runtime.evaluate with any JS expression
# Get the result back as JSON

The JavaScript executes inside your real browser tab — with full access to the DOM, service workers, PushManager, localStorage, Notification.permission, everything your debug panel reads. The difference is the AI is calling it directly rather than waiting for you to relay.


Zero Dependencies

The CDP wire protocol is two things: HTTP for tab discovery, WebSocket for command execution. Both are available in Node without installing anything.

// List tabs
const tabs = await fetch('http://localhost:9222/json').then(r => r.json());
const target = tabs.find(t => t.type === 'page');

// Connect
const ws = new WebSocket(target.webSocketDebuggerUrl);

// Execute JS in the browser tab
ws.send(JSON.stringify({
  id: 1,
  method: 'Runtime.evaluate',
  params: {
    expression: 'Notification.permission',
    returnByValue: true,
  }
}));

// Result arrives as a WebSocket message: { id: 1, result: { result: { value: 'granted' } } }

No Playwright. No Puppeteer. No ws package. Node 21+ has WebSocket built-in behind --experimental-websocket. Chrome handles the rest.


Wiring It Into Your CLI

The pattern that makes this usable session-to-session is adding it to your project CLI rather than running raw commands.

Two commands cover the full workflow:

debug:launch — restarts Chrome with the debug port, opens your local dev URL. Three-second countdown so you can cancel. Keeps your profile intact.

debug:drive — connects via CDP, opens the debug panel in the target tab, reads all section states, prints them as structured terminal output. Optionally clicks an action button.

ab debug:launch
# → restarts Chrome with --remote-debugging-port=9222
# → opens localhost:4000/?debug

ab debug:drive
# → Push Notifications
#    ● Subscription Status
#      permission        granted
#      subscribed        true
#      endpoint          https://fcm.googleapis.com/…
#      actions: Unsubscribe · Send test push

ab debug:drive --action "Push Notifications" "Subscribe"
# → clicks Subscribe in your real browser tab
# → reads and prints updated state

The click executes in your actual browser. You see the result. The AI sees the updated state on the next read. No relay needed.


Reading Your Own Debug Panel

The debug:drive command works by running DOM-reading JavaScript inside the browser tab via Runtime.evaluate. It queries the debug panel’s card structure directly:

function readPanel() {
  const sections = [];
  document.querySelectorAll('.dbg-sec').forEach(sec => {
    const cards = [];
    sec.querySelectorAll('.dbg-card').forEach(card => {
      cards.push({
        title:   card.querySelector('.dbg-card-title')?.textContent,
        status:  card.classList.contains('ok') ? 'ok' : ...,
        rows:    [...card.querySelectorAll('.dbg-row')].map(r => ({ ... })),
        actions: [...card.querySelectorAll('.dbg-btn')].map(b => b.textContent),
      });
    });
    sections.push({ title: ..., cards });
  });
  return sections;
}

This means the debug panel itself needs no changes. Whatever your panel renders, the AI can read. Add a new plugin to the panel, it shows up in debug:drive output automatically. The CDP layer is a reader over the DOM — it doesn’t care what the panel shows, only that it’s there.


The Decision Tree

When you build a debug tool, apply this filter:

State lives in Right wire
A file or build output Direct read — AI can already access it
A server or API REST endpoint — simple, session-persistent
The browser CDP — Chrome already speaks it, zero install

Manual relay is always the fallback. It works. It shouldn’t be the default.

For browser state specifically, the instinct to build a bridge server or WebSocket relay is understandable but backwards. Chrome is the bridge. CDP is the protocol. The only setup is one flag at launch time and one script in your project CLI.


What Changes

Every debug panel built for human eyes is one --remote-debugging-port flag away from being AI-readable.

Every debugging session that currently involves you relaying browser state to the AI can become a direct read. The AI queries your real browser, sees the same state you see, and can take action — clicking buttons, clearing storage, triggering subscriptions — without waiting for you to relay each step.

The bottleneck isn’t the AI’s capability. It’s the missing wire. CDP is the wire. It’s been sitting there the whole time.


Next: 031 — TBD


Join the 10xdev Community

Subscribe and get 8+ free PDFs that contain detailed roadmaps with recommended learning periods for each programming language or field, along with links to free resources such as books, YouTube tutorials, and courses with certificates.

Audio Interrupted

We lost the audio stream. Retry with shorter sentences?