← Blog
20 August 2026 · Jack Williams

What an AI Agent Needs to Fix a Production Bug

Collecting errors is the easy part. Getting them into a shape an AI agent can act on means source maps, first-party frames, severity over volume, and knowing which deploy caused it. Lessons from building an MCP server for production monitoring.

Something breaks on your site. You open the console, copy the stack trace, paste it into your agent, then write a paragraph explaining what the user was doing when it happened. The agent fixes it in about thirty seconds.

The fix took thirty seconds. Getting the agent to a position where it could fix anything took ten minutes, and every one of those minutes was you working as a data pipe between production and a model.

I have spent the last few months building monitoring designed to delete that step, and most of what I learned had nothing to do with collecting data. Collecting is the easy part. The hard part is that nearly all monitoring data is shaped for a human staring at a dashboard, and that shape is close to useless to an agent. Here is what actually has to change.

A minified stack trace is worth nothing

This is the obvious one, and it is still the most common failure.

An error from a production bundle looks like this:

rX@https://example.com/_next/static/chunks/4bd1b696-785d557922aa1344.js:1:36664
sg@https://example.com/_next/static/chunks/4bd1b696-785d557922aa1344.js:1:136754

A human can at least tell that something went wrong. An agent can do nothing with it. It cannot open the file, it cannot map the column offset back to a line of your source, and if you hand it this it will guess. A guessing agent is worse than no agent, because it produces a confident, plausible, wrong diff.

Source maps fix this, but where you apply them matters. Resolving them in the browser is too late and too expensive. Upload your maps at build time, keyed by release, and resolve the stack server-side before anything reads it. The agent should never see the minified version.

The difference in output is the difference between a guess and a fix:

useErrorBoundary  src/components/ErrorReporter.tsx:41:12
ErrorsView        src/components/views/ErrorsView.tsx:70:8

The top frame is almost never your code

Once source maps work, a subtler problem appears. You resolve nine frames, every one maps perfectly, and all nine sit inside node_modules.

React hydration errors are the clearest example. The stack is entirely framework internals, because the throw genuinely happened inside React. If you take the top frame and call it the error location, which is what most tools do by default, you hand the agent a file path inside react-dom and it has no way to know which of your components caused it.

The fix is to classify frames by path. Anything under node_modules or a vendor chunk is system code. Find the first frame that is not, and make that the location. Keep the vendor frames, but put them after it, not in front.

This one change does more for agent usefulness than almost anything else, because it converts "something in React broke" into "this component broke."

Ranking by volume points the agent at the wrong bug

A busy site produces a lot of errors. One I look at regularly has around ninety distinct error groups in a given week. Sort them by occurrence count and the top of the list is a third-party 404 firing three thousand times, while the real crash affecting forty users sits somewhere on page two.

For a human that is an annoyance. For an agent it is fatal, because the agent works the list from the top and you have told it the wrong thing is important.

Volume is one input, not the ranking. What matters is a combination:

  • Reach, but log-scaled. Four thousand affected users is not ten times worse than four hundred.
  • What kind of error it is. An uncaught exception is a crash. A handled 404 on an analytics beacon is not.
  • Whether it is still happening. An error that stopped three days ago should sink, however loud it once was.
  • Whether it came back after being fixed. A regression deserves to jump the queue.

Score those into a single number and rank on that. The specific formula matters less than the principle: the agent needs a defensible answer to "what should I work on first" rather than "what is loudest."

When it started is often the entire answer

The most useful field I added was also one of the simplest: the release an error first appeared in.

I had a hydration error on my own dashboard that I had been ignoring. When I finally looked, the interesting part was not the stack trace. It was that the error first appeared three months earlier, in a deploy I had long forgotten, and not in anything I had shipped that week. Without that, I would have spent an afternoon bisecting recent commits for a bug that predated all of them.

Attributing an error to the deploy that introduced it is straightforward if you tag every event with a release identifier at build time, usually the commit SHA. Take the earliest occurrence of each error group, look at which release it carried, and that is your answer.

Get this wrong in a subtle way and it is worse than not having it. If your "first seen" is scoped to the query window but your "introduced in" searches all time, the two fields contradict each other and the agent chases the wrong deploy. Make sure both answer the same question.

Noise is not neutral

Browser extensions throw errors into your page. Ad scripts fail. Cross-origin scripts produce the famously useless Script error. with no stack at all.

None of this is your code and none of it is fixable by you, but it lands in the same stream as real bugs. For a human it is clutter. For an agent it is an active waste, because it will dutifully try to fix a problem inside someone else's minified ad script.

Classify origin at ingest, and be conservative. Match on known signatures: extension URL schemes, known ad and analytics hosts, the specific cross-origin error shapes. Do not classify by hostname alone, or you will bury real first-party errors from anyone serving their own JavaScript off a CDN.

Then fold the noise away by default, but keep it reachable. It is still evidence, it is just not work.

A false positive costs more than a missing error

This one I learned by shipping the bug.

I added network error tracking so failed API calls would appear alongside JavaScript errors. Reasonable feature. Within a day my own dashboard was full of errors like GET /api/live 0 and POST /api/perf 0.

Status zero. Not a server failure, not a timeout. Those were requests still in flight when the user navigated away, and a cancelled request resolves with status zero exactly as a network failure does. On any single-page app this happens constantly.

The fix is to track a page-unload flag and check for AbortError, then skip anything cancelled rather than reporting it. Genuine offline failures still come through.

The wider lesson is about trust. A human learns to ignore a category of false alarm within about a day. An agent does not. It treats your data as ground truth, so every false positive is work it will actually attempt. If you are building for agents, a quiet stream that can be trusted beats a complete one that cannot.

The error is rarely enough on its own

TypeError: Cannot read properties of undefined tells you almost nothing. The same error, plus the fact that the user clicked Checkout, after a request to /api/cart returned 500, on Safari, two minutes after a deploy, is a diagnosis.

Capture a short breadcrumb trail on the client: navigations, clicks on interactive elements, console errors, failed requests. Cap it at a couple of dozen entries, truncate the strings, and ship it with the error payload. It costs very little and it changes the character of what you can ask.

The same applies to framework context. If an error boundary caught the error, the component stack tells you which tree threw, which a pure JavaScript stack often cannot.

Then, and only then, the plumbing

Everything above is about the shape of the data. Exposing it to an agent is comparatively easy: an MCP server, OAuth for auth, one tool per question you want answerable.

Two things worth passing on. Tool descriptions matter more than you expect, because the agent chooses what to call by reading them, and a well-built endpoint with a vague description never gets called. And design tools around questions rather than tables. "List errors ranked by severity" and "what did this deploy introduce" are useful. "Query the errors table" is not, because now the agent has to learn your schema before it can help.

What this does not solve

Plenty. There is no distributed tracing here, so backend coverage means server-side errors rather than spans across services. Correlating a conversion drop with an error spike and a specific deploy is something I can do by hand today but have not automated. And an agent with production visibility still writes code you should read before it ships.

But the copy-paste step is gone, and that turns out to be most of the friction. Asking what is broken and getting back a ranked list with real file paths, the user's journey, and the deploy that caused it is a different working relationship than pasting console output into a chat window.

If you want to skip the build, this is what JAMP does: analytics, errors, uptime and Core Web Vitals in one place, cookieless, with all of it exposed over MCP. Error tracking is free and unmetered. If you would rather build your own, the guides on error tracking with source maps and Core Web Vitals in Next.js cover the collection side in plain JavaScript.