> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abtestly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and telemetry

> What ABTestly logs when things go wrong, how first-occurrence dedup keeps a bad variant from flooding the /err endpoint, and where you can see the errors.

Errors in variant code, in ABTestly's own runtime, or in things like
Global JS are captured and reported. The design goal is to give you
enough signal to fix a broken variant without flooding the ingest
pipeline with duplicates.

## The `/err` beacon

Every error goes to `POST /err`. The payload is:

```json theme={null}
{
  "s": "<siteId>",
  "v": <configVersion>,
  "t": <clientTimestampMs>,
  "errors": [
    {
      "e": "<experimentId>",
      "vk": "<variantKey>",
      "ph": "apply" | "cleanup" | "route-handler" | "variant-js" | "async" | "global-js",
      "m": "<messageString>",
      "st": "<stackString>",
      "u": "<pageUrl>"
    }
  ]
}
```

## First-occurrence-per-session dedup

The snippet keeps a per-session set of `(phase, experimentId)`
tuples that have already reported. The first occurrence sends. The
second, third, and Nth occurrences in the same session do not.

This exists because a broken variant that runs on every page load
would otherwise flood the endpoint. One report per session is enough
signal for diagnosis without the flood.

## The phases

* **apply**, an error inside `onApply`.
* **cleanup**, inside `onCleanup`.
* **route-handler**, inside the runtime's SPA route change handler.
* **variant-js**, inside the variant's top-level JS (not wrapped in
  a handler).
* **async**, a rejected Promise the variant kicked off with no
  `.catch`.
* **global-js**, inside the Global JS block.

## Where errors show up on your side

* **Sentry**, if you have SENTRY\_DSN configured (Enterprise), errors
  land in your Sentry project.
* **Dashboard health panel**, the site's Snippet health panel shows
  a count of recent errors and lets you drill into the most recent
  ones with their stacks.
* **Dev debugger**, `window.__abtestly.debug()` on the affected
  page shows the error in the events buffer alongside the events
  that preceded it. Best for reproducing.

## What happens after an error

An error in `apply` or `variant-js` **does not stop the runtime**.
The runtime catches, logs, and moves on. Other experiments on the
page still get evaluated and applied. The specific variant that
threw simply did not apply.

An error in `global-js` also does not stop the runtime. The variants
that were expecting Global JS to define something might throw
themselves as a consequence, but each is caught independently.

## Errors from your own analytics adapter calls

If your GA4 or Segment or Amplitude SDK is missing or throws, the
[analytics adapters](/settings/analytics-adapters) catch it and
report as an error under phase `variant-js` (the wrapping is
conservative). One first-occurrence report per session.

## What is *not* in `/err`

* **Bucketing decisions.** "Visitor did not enter test" is not an
  error; it is a normal decision. Debug those with the dev debugger.
* **Failed beacons.** The `/e` beacon uses `sendBeacon` with a
  `keepalive fetch` fallback. If both fail, the runtime does not
  self-report, that would require the same broken network to
  succeed at `/err`.
* **User-perceived issues.** "The layout looks wrong" or "the button
  is off-center", those are QA problems, not runtime errors. Use
  the [QA overlay](/preview/qa-overlay).

## Rate limiting

`/err` is not user-authenticated, so the dedup on our side is the
only rate limit. If you push a variant that throws on every page
load, expect one report per (session, experiment), a burst of new
visitors in the first minute after a bad publish will produce a burst
of reports.

If you know you are pushing something risky, republish quickly when
you notice, the dedup resets per session, so subsequent visitors
will not report against the fixed version.
