2026-08-17 · 4 MIN

I turned on error tracking. Most of it was Outlook.

— WRITING

+

2026-08-17 · 4 MIN

A weekly report landed on August 17th telling me my sites had thrown 363 exceptions, up 156 percent from the week before, with crash-free sessions at 94.29 percent. That is the kind of number that reorganises a day. Something is broken badly enough that roughly one visit in eighteen hits it, and I have no idea which page.

333 of those 363 were not my code. Not a subtle bug in my code, not a dependency of my code. Two browser artifacts and a dev server.

What was actually in there

The products involved have nothing to do with each other. AgentWire is a curation lane for agentic developer tooling, MCP servers and coding-agent harnesses, and its site is a 1:1 reflection of what the accounts actually posted. KitGrade is a directory of SaaS starter kits scored from measured facts, where every kit is labelled hands-on or documented and the label records whether the kit was cloned onto a machine and built. Different data, different stacks underneath, different everything. The only thing they share is the analytics snippet.

327 of the 363 were this string:

Non-Error promise rejection captured with value: Object Not Found Matching Id:N,
MethodName:update, ParamCount:4

That is not from any code I wrote. It comes from the wrapper Microsoft puts around links opened out of Outlook, running inside the page it just opened. Which is why it fired on AgentWire and KitGrade and everything else at the same moment: an error that appears simultaneously across products sharing no code is not coming from any of them. That simultaneity is the only tell you get, and you only get it if you have more than one site reporting into the same place.

6 more were ResizeObserver loop completed with undelivered notifications, which is the notice a browser emits when a layout observer runs long. Nothing is on fire. Nobody sees anything.

The remaining 30 were mine, in the least useful sense. They were a dev server on http://localhost:3737 reporting compile errors into the production project while I was mid-edit: PAD_X is not defined, SelectedWork is not defined. Errors I had already fixed before the report was written, filed alongside real ones.

I turned on error tracking. Most of it was Outlook. — code

I turned on error tracking. Most of it was Outlook. — code

The part that generalises

Turning on browser exception capture is one boolean now. In posthog-js, which I am on at 1.407.5, it is capture_exceptions: true next to the pageview settings. You add a line, you deploy, and a week later you have a health metric.

The metric is wrong out of the box, and it is wrong in the direction that costs you the most. It reads worse than reality, so the first thing it makes you do is go looking for a crash that is not there. Then, once you have learned that the report is noise, it reads as noise forever, and the first real one arrives in a mail that looks exactly like the previous four.

The dev server half is worth its own sentence. The project key lives in the client bundle, so it is the same key on localhost, on a preview URL and in production. Every environment reports into one place unless you tell it not to.

I turned on error tracking. Most of it was Outlook. — architecture

I turned on error tracking. Most of it was Outlook. — architecture

The gate

posthog-js has a before_send hook that runs in the browser and drops the event when you return null. I put the filter there rather than in the dashboard, because a dropped event costs nothing and a filtered one still has to be sent, stored and then excluded from every view forever after.

before_send: (event) => {
  if (!event || event.event !== '$exception') return event;
  try {
    const host = window.location.host.split(':')[0].toLowerCase();
    if (host === 'localhost' || host.endsWith('.localhost') || /^[\d.]+$/.test(host) || host.endsWith('.vercel.app')) return null;
    const v = JSON.stringify(event.properties?.$exception_values ?? '');
    if (/Object Not Found Matching Id:\d+/.test(v)) return null;
    if (/ResizeObserver loop/.test(v)) return null;
  } catch {
    // a noise gate must never be the thing that breaks a page
  }
  return event;
},

Three details in there I would keep if I wrote it again.

The first line checks event.event !== '$exception' and returns early. before_send sees everything, including pageviews and clicks, so a filter written loosely here quietly eats your entire analytics feed instead of your error feed.

The host check drops localhost, any .localhost subdomain, bare IP addresses and preview deployments. That is the environment split I did not get for free.

And the catch is the one I care about most. This function runs inside the page, on every event, before anything is sent. If it throws, it throws in front of the visitor. A filter that exists to protect a dashboard is not allowed to be the reason a page stops working, so it fails open and lets the event through.

The two regexes are the least durable part of this. They match strings that a browser vendor can change without telling anyone, and when that happens the noise comes back and I will have to read a digest carefully to notice. I would rather that than a filter loose enough to swallow something real.

The number I get now is 30 exceptions smaller and it is entirely mine. That is the point of the exercise: not a better crash rate, a crash rate that is about my software.

https://agentwire.kynth.studio/?utm_source=founder-devto&utm_medium=social

← All writing