Menu
Blog Documentation Community Pricing Demo Call Sign Up
Sign Up

Closing the Loop in Agentic Software Development

How to pipe browser console logs to disk so AI coding agents can see runtime errors, failed fetches, and console output during development.

General

This post was written by an engineer at QueryPlane. QueryPlane is an app builder for your database: bring your own postgres db and you can create interactive applications to share with other developers, coworkers or even your customers. If you’re interested in trying it out, get started here.


AI coding agents can read your source code, run your test suite, and execute shell commands. But there’s a gap most teams overlook: agents are blind to the browser. When a React component throws a runtime error, when a fetch call fails silently, when a console.error fires deep inside an effect — that information lives in the browser’s DevTools console, a place no agent can access.

Server-side logs are straightforward. Pipe stdout to a file and point the agent at it. Browser logs are the hard part. The console API runs in a sandboxed browser environment with no direct path to the filesystem. Bridging that gap requires injecting code that intercepts console calls, batches them, sends them to a local dev server endpoint, and writes them to disk.

This post covers how we solved this at QueryPlane — and how you can do the same.

In this post, we’ll cover:

  • The problem - Why agents can’t debug runtime issues without log access
  • Server-side logs - Piping backend and dev server output to files with tee
  • Browser console logs - Using agent-tail and alternatives to capture frontend logs
  • Teaching your agent - Updating CLAUDE.md, .cursorrules, and agents.md so your agent knows where to look

The problem

Consider what happens when you ask an agent to fix a bug that only manifests at runtime. The agent can read the component source, trace the data flow, and even reason about what should happen. But if the actual error is a CORS failure, a serialization TypeError, or a stale closure — information that only appears in the browser console — the agent has no way to see it. It’s left guessing, or worse, asking you to copy-paste the error.

This creates a broken feedback loop. A human developer works with two windows: an editor and a browser with DevTools open. The agent only gets one. Without log access, the agent can’t verify that its fix actually worked, can’t spot cascading errors, and can’t distinguish between a build failure and a runtime failure.

The fix is simple in principle: write all logs — server and browser — to files on disk that the agent can read. In practice, the server side is trivial and the browser side requires a small amount of plumbing.

Server-side logs

Server process output — your backend, your Vite dev server, your docs server — already writes to stdout and stderr. The only thing needed is to persist it to a file while keeping it visible in your terminal.

The Unix tee command does exactly this. It reads from stdin and writes to both stdout and a file simultaneously. Here’s the pattern from our development Makefile:

backend:
	@mkdir -p tmp/logs/server
	cd backend && air 2>&1 | tee ../tmp/logs/server/backend.log

frontend:
	@mkdir -p tmp/logs/server
	cd frontend && npm run dev 2>&1 | tee ../tmp/logs/server/frontend.log

The 2>&1 merges stderr into stdout so both streams get captured. The mkdir -p ensures the log directory exists before tee tries to write to it. Each service gets its own log file, and you can tail any of them independently.

This approach is framework-agnostic. It works for Go servers, Node processes, Python backends, Ruby — anything that writes to stdout. There’s no library to install, no configuration to maintain. The logs are plain text files that any agent (or grep) can read.

For projects with multiple services, agent-tail provides a CLI that wraps this pattern with automatic session management and a combined log:

npx agent-tail run 'api: cd backend && air' 'fe: cd frontend && npm run dev'

This creates a session directory under tmp/logs/latest/ with individual log files per service plus a combined.log that interleaves them with prefixed labels.

Browser console logs

Browser logs are fundamentally different from server logs. The console API runs inside a browser sandbox with no filesystem access. Getting those logs into a file requires three things: intercepting console calls in the browser, sending them to a local server endpoint, and writing them to disk.

There are several tools that solve this. They differ in how they’re configured, what frameworks they support, and where the logs end up.

agent-tail (Vite plugin)

agent-tail is purpose-built for this use case. Its Vite plugin injects a script during development that patches console.log, console.warn, console.error, and other console methods. Intercepted calls are batched and flushed to a local endpoint, which writes them to browser.log in a session directory.

Installation:

npm install -D vite-plugin-agent-tail

Configuration in vite.config.ts:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { agentTail } from 'vite-plugin-agent-tail'
import path from 'path'

export default defineConfig(({ mode }) => ({
  plugins: [
    react(),
    mode === 'development' && agentTail({
      logDir: path.resolve(__dirname, '..', 'tmp', 'logs', 'browser')
    }),
  ],
}))

The mode === 'development' guard ensures the plugin is never active in production builds. Logs are written to tmp/logs/browser/latest/browser.log (the latest symlink always points to the most recent session).

The plugin accepts several configuration options:

OptionDefaultDescription
logDir"tmp/logs"Directory for log storage
logFileName"browser.log"Log file name within each session
maxLogSessions10Max session directories to retain
flushInterval500Client-side flush interval (ms)
maxBatchSize50Max batch size before immediate flush
levels["log", "warn", "error", "info", "debug"]Console methods to intercept
captureErrorstrueCapture unhandled window.onerror events
captureRejectionstrueCapture unhandled promise rejections
excludes[]Patterns to exclude from logs

agent-tail also has a Next.js plugin that requires wrapping your Next config and adding an API route handler, since Next.js doesn’t have Vite’s middleware pipeline.

vite-plugin-terminal

vite-plugin-terminal, created by Matias Capeletto (a Vite core team member), takes a different approach. Instead of patching global console, it exposes a virtual module you import explicitly:

import { terminal } from 'virtual:terminal'

terminal.log('This appears in the Node terminal, not the browser')

You can also redirect all console calls to the terminal with the console: 'terminal' option:

import Terminal from 'vite-plugin-terminal'

export default {
  plugins: [
    Terminal({ console: 'terminal' })
  ]
}

The tradeoff: logs go to your terminal’s stdout rather than a dedicated file. If you’re already piping your Vite dev server output to a file with tee (as shown in the server-side section), this effectively captures browser logs too — they’ll appear in your frontend.log alongside Vite’s own output. The downside is that browser logs and server logs are interleaved in the same file, making them harder to filter. Terminal calls are also automatically stripped from production builds.

vite-console-forward-plugin

vite-console-forward-plugin, created by Armin Ronacher (creator of Flask), patches the global console object and forwards all calls to the Vite dev server’s log output. Like vite-plugin-terminal with the console: 'terminal' option, it sends browser logs to the server terminal — meaning they end up wherever your Vite process output goes.

import { consoleForwardPlugin } from 'vite-console-forward-plugin'

export default {
  plugins: [
    consoleForwardPlugin({
      levels: ['log', 'warn', 'error'],
    })
  ]
}

It supports log buffering, stack traces for errors, and configurable log levels.

Which approach to use

If you want dedicated browser log files separate from server output, use agent-tail. Its file-based approach gives agents a clean, greppable target with no noise from HMR updates or Vite internals.

If you’re already piping dev server output to files with tee and want something simpler, vite-plugin-terminal or vite-console-forward-plugin both work — browser logs will end up in your server log file alongside Vite output.

For Next.js projects, agent-tail is currently the only option with a dedicated plugin.

Teaching your agent where to look

Capturing logs is only half the solution. The agent also needs to know the logs exist, where they live, and when to check them. Most AI coding agents support a project-level instruction file — CLAUDE.md for Claude Code, .cursorrules for Cursor, and agents.md for OpenAI Codex. This is where you tell the agent about your log infrastructure.

Claude Code (CLAUDE.md)

Claude Code reads a CLAUDE.md file at the root of your repository. Add a section describing your log files:

## Dev Server Logs

When debugging runtime issues, read the dev server log files:

- **Browser console logs** (console.log/error/warn from React code):
  `tmp/logs/browser/latest/browser.log`
- **Server process logs:**
  - Backend (Go/air): `tmp/logs/server/backend.log`
  - Frontend (Vite): `tmp/logs/server/frontend.log`

This is enough for Claude Code to know it should check tmp/logs/browser/latest/browser.log when investigating a runtime bug, or grep through tmp/logs/server/backend.log when an API call fails.

Cursor (.cursorrules)

Cursor reads a .cursorrules file in your project root. The format is the same — plain text instructions:

## Debugging

Log files are available in the tmp/logs/ directory:

- Browser console output: tmp/logs/browser/latest/browser.log
- Backend server logs: tmp/logs/server/backend.log
- Frontend dev server logs: tmp/logs/server/frontend.log

When a user reports a runtime error or a UI bug, read the browser
log file first to check for console errors, failed network requests,
or unhandled exceptions. For API errors, check the backend log.

OpenAI Codex (agents.md)

Codex reads an agents.md file. Same idea, slightly more explicit about when to use the logs:

## Log Files

Dev server logs are piped to disk. Use these to debug runtime issues:

- `tmp/logs/browser/latest/browser.log` — Browser console output
  (console.log, console.warn, console.error, unhandled exceptions).
  Read this file when debugging UI bugs or client-side errors.
- `tmp/logs/server/backend.log` — Backend server stdout/stderr.
  Read this file when debugging API errors or server crashes.
- `tmp/logs/server/frontend.log` — Vite dev server output.
  Read this file for build errors, HMR issues, or plugin warnings.

What to include

Regardless of which agent you use, the instructions should answer three questions:

  1. Where are the logs? — Exact file paths relative to the project root.
  2. What’s in each file? — Browser console output vs. server stdout vs. build output. An agent that doesn’t understand the distinction will look in the wrong file.
  3. When to check them — “When debugging runtime issues” or “when a UI bug is reported” gives the agent a trigger. Without this, the agent may never think to look.

Wrapping up

The gap between what a human developer sees and what an AI agent sees is mostly about log access. Server-side logs are a one-liner with tee. Browser logs require a plugin — agent-tail for file-based capture, or vite-plugin-terminal / vite-console-forward-plugin for terminal forwarding. And the agent needs explicit instructions in CLAUDE.md, .cursorrules, or agents.md telling it where to look.

Once this is in place, the feedback loop closes. The agent can make a change, check the browser logs to see if the error resolved, and iterate — the same workflow a human developer follows with DevTools open.