# Telling a real incident from a bot, a browser extension or a stale tab

> Most of a new project's error feed is not your bug. The four signatures of noise, how to filter each one at the right layer, and the three signals that mean it is real.

You wire up Sentry on a Friday. By Monday there are 3,000 events across 60
issues, and the first four you open are:

- `TypeError: Cannot read properties of null (reading 'querySelector')` from a
  file called `inject.js`
- `Loading chunk 4821 failed`
- `Error: ENOENT: no such file or directory, open '/.env'`
- `AbortError: The operation was aborted`

None of them is a bug in your app. If you spend the morning on them you will
learn nothing and, worse, you will start ignoring the feed, which is how the
real incident on Thursday goes unnoticed for six hours.

## The four signatures of noise

### 1. Browser extensions and injected scripts

**Looks like:** stack frames from `chrome-extension://`, `moz-extension://`,
`safari-web-extension://`, or files you have never heard of (`inject.js`,
`content.js`, `gtm.js`). Errors about DOM elements that do not exist in your
markup. Often a wide spread of browsers and a single event per user.

**Why it happens:** an extension runs in the page, throws, and your global
handler catches it. It is genuinely not your code.

**Filter at:** `denyUrls` in `src/instrumentation-client.ts`.

```ts
denyUrls: [/extensions\//i, /^chrome:\/\//i, /^moz-extension:\/\//i, /^safari-web-extension:\/\//i],
```

**Do not** dismiss too fast: an extension error that only happens on *your*
checkout page can still mean your DOM broke an assumption it makes for
everyone. Check whether the issue is spread across routes or concentrated on
one.

### 2. Stale tabs after a deploy

**Looks like:** `Loading chunk N failed`,
`Failed to fetch dynamically imported module`, `Unexpected token '<'` from a
`.js` URL. A sharp spike immediately after each deploy, then decay over a few
hours.

**Why it happens:** a user's tab has been open since before the deploy. It asks
for a chunk hash that no longer exists, gets a 404 (or your HTML 404 page,
hence the `<`), and fails.

**Filter at:** `ignoreErrors`. It is already there in this configuration.

**But also fix the product problem:** the user's tab is broken. Catch chunk
load errors in the client and prompt a reload, or use a router-level error
boundary that recovers by navigating. The error is noise; the broken session is
not.

### 3. Bots, scanners and vulnerability probes

**Looks like:** server-side errors on paths you never wrote, `/wp-admin`,
`/.env`, `/phpmyadmin`, `/.git/config`, `/api/v1/../../etc/passwd`. Malformed
JSON bodies. `null` user on every event. A handful of IPs, or a thousand. Often
a burst at a strange hour.

**Why it happens:** the internet scans everything. Within a day of a domain
going live it is being probed continuously.

**Filter at:** ideally before your app: a WAF or platform-level rule. Failing
that, `ignoreErrors` on the specific parse errors, plus making sure your 404
path does not throw.

**The thing to actually check:** a scanner hitting a route that *exists* and
getting a 500 is a real finding. `/api/users?id=1%20OR%201=1` returning a
database error means your route is not validating input, and the bot just did
your security review for you.

### 4. Users who left

**Looks like:** `AbortError`, `The operation was aborted`, `ECONNRESET`,
`ERR_STREAM_PREMATURE_CLOSE`, `ClientClosedRequest`. Frequently on long
requests: streaming responses, uploads, slow searches.

**Why it happens:** the client navigated away or closed the tab. The
in-flight request was cancelled. Nothing is wrong.

**Filter at:** `ignoreErrors` on the server config, and check `error.name ===
"AbortError"` before capturing in your own handlers.

**But:** a *rise* in abort rate on one route is a real signal. Users abort
requests that are too slow. If aborts on `/search` triple, that is a latency
regression wearing a disguise.

## The three signals that it is real

Against those four, the shape of a genuine incident:

**1. It correlates with a deploy.** Sentry shows first-seen release. If an
issue's first event is the release you shipped twenty minutes ago and the
count is climbing, stop reading and go look at that diff. This single signal
resolves most real incidents.

**2. Multiple distinct users, in a short window, on one route.** Noise is
usually one user many times, or many users once across many routes. A real
break is many users, on one path, right now. Sort by user count, not event
count.

**3. The stack trace is in your code.** The top frames are your files, your
function names, your line numbers, not a vendor bundle, not an extension.
Combined with signal 1, this is close to conclusive.

## A triage rule that holds up

For each new issue at the top of the feed, in order:

1. **Is the top frame my code?** No → probably noise; classify it as one of the
   four and filter it at the right layer.
2. **Is it new since a recent release?** Yes → treat as a regression until
   proven otherwise. Read the diff before the stack trace.
3. **How many distinct users?** Many → incident. One → interesting, not urgent.
4. **Is it growing, flat or decaying?** Growing → act now. Decaying → probably
   a stale-tab spike, already over.
5. **Can I reproduce it?** No → do not ship a speculative fix. A wrong fix
   changes the stack trace, which creates a *new* issue and makes it look
   resolved.

## Suppress with a reason, and review it

Every filter you add is a decision to be blind to something. That is fine, and
it needs a comment:

```ts
ignoreErrors: [
  // A user navigated away mid-request. Not actionable.
  "AbortError",
  // Stale tab holding a pre-deploy chunk hash. See the reload prompt in
  // src/components/chunk-error-boundary.tsx.
  "Loading chunk",
],
```

An `ignoreErrors` entry with no explanation is how a real error gets silently
dropped two years from now, when the string happens to match something new.

Re-read the list every few months. Filters accumulate, browsers change, and the
entry that was noise in one release can be masking a bug in the next.

## Get to zero, then keep it there

The goal is not a small number of issues; it is a feed where **every unresolved
issue is something you intend to act on**. That state is worth real effort to
reach, because it changes what an alert means: a notification from a clean
project is information, and a notification from a noisy one is a reflex you
have already learned to ignore.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
