Crossdeck Docs
Dashboard

Alerts & triage

Errors 9 min read · One email per new issue, never per event

A burst of errors is only useful if it arrives as one decision, not fifteen interruptions. Crossdeck groups every captured error into an issue by fingerprint, computes a priority that weighs paying-customer impact, and notifies you under a small set of rules that hold no matter how loud an incident gets: one email per new issue, spikes measured against an issue's own baseline, and muted means nothing fires. This page is that contract, written down.

TL;DR

From error to issue

Every error.* event that reaches Crossdeck is stamped with a fingerprint before it touches storage — an opaque SHA-256 hash of a fixed, versioned tuple. All events sharing a fingerprint accumulate on a single issue: one row in your Issues list, one count, one triage state.

The fingerprint is computed from exactly four inputs:

InputWhy it's in the hash
Event typeerror.unhandled, error.http, and error.unhandledrejection group separately. An unhandled exception and an HTTP 500 in the same function are different bugs.
Exception classThe runtime symbol — TypeError, HTTPError. A code identifier, not user data.
Top frame functionWhere the error originated. Anonymous functions collapse to a single placeholder.
Top frame file, normalizedBuild hashes (main-abc123.jsmain-def456.js), query strings, emails-in-URLs, and UUIDs-in-paths are stripped first, so a re-deploy doesn't mint a new issue for an old bug.

Just as important is what is never hashed: the exception message, stack frame bodies, breadcrumbs, URL query values, user and session IDs, timestamps. Messages routinely carry per-occurrence data — "Cannot read 'X' of null" where X is user input — and hashing them would split one bug into an issue per value. Two different users hitting the same logical bug always land on the same issue.

Issue statuses

An issue is always in exactly one of four states:

StatusHow it gets thereBehavior
openEvery new issue starts here.Shows in the Open tab. Alerts fire per the contract below.
resolvedYou click Resolve.Leaves the Open tab. The fingerprint keeps watching for new events.
regressedAutomatic — a new event lands on a resolved issue.Returns to the Open tab with a Regressed badge and a status-history entry. Triggers a regression alert.
ignoredYou click Ignore.Hidden from the Open list, silenced everywhere, keeps counting under the Ignored tab. Unignore restores it.

Regressed is the loudest signal Crossdeck has. A regression means you looked at the bug, decided it was fixed, and the code disagrees. The alert decider evaluates a regression before every other alert type, and the issue surfaces back in your Open tab automatically — you never re-open it by hand to find out a fix didn't take.

Priority

Every issue carries a computed priority — high, medium, or low — recomputed on every event, so an issue that escalates from 10 events an hour to 200 climbs on its own, and falls back when the surge dies down.

The base tier comes from how the error was captured:

Then each of these modifiers bumps the tier up by one (ceiling at high):

Two floors hold underneath the blend. Un-actionable noise — ad-blocker hits, browser-extension errors, aborted requests mid-navigation — is pinned to low and no per-event modifier can raise it; only a genuine anomaly (a spike, or sudden breadth across many users) lifts it, because a flood of "noise" hitting everyone at once is your outage alarm. And connection noise (HTTP status 0 — the request never reached a server) is a hard floor nothing pierces.

Manual override

The Priority select on the issue page (Auto / High / Medium / Low) pins a manual priority. Crossdeck never overwrites a manual priority — the recompute keeps running underneath, but display and sort use your pin until you set it back to Auto. When the pinned value differs from the computed one, the priority pill says so.

Default sort

The Issues list opens on the Open tab sorted by most paying customers affected. Priority, recency, frequency, and newest-issue sorts are one dropdown away, but the default ordering is the business question: which of these is costing me revenue right now?

The notification contract

These are commitments, not defaults. Each one is enforced by a state machine on the issue itself, so they hold under concurrency, retries, and incident-scale volume.

1. One email per new issue

The first occurrence of a new fingerprint sends one email. A burst of fifteen events in two minutes produces one email, not fifteen. If that first send fails, Crossdeck retries up to 3 attempts, spaced at least one minute apart — and sends no other alert type for the issue until the first email is settled.

2. Re-alerts only when something meaningful changed

After the first email, you hear about an issue again in exactly three cases:

On top of those triggers sits a hard cap: at most 4 emails per issue in any rolling 24-hour window, no matter what. (Spike notifications are the one exemption — see channels below.)

3. Muted or ignored means nothing fires

Muting an issue, or setting its status to ignored, short-circuits every notification decision before anything else is evaluated. No email, no Slack message, no exception for spikes. Ignoring is your severity verdict — "this is unactionable" — and volume doesn't change unactionable into actionable. Sixty events becoming six hundred is still just a number quietly incrementing under the Ignored tab.

One nuance: a genuine spike inside an ignored or muted pattern is still recorded. It surfaces as a quiet chip on the Issues page — "Ignored pattern spiking — review?" — that links to the Ignored tab. Crossdeck may escalate visibility; it never overrides your verdict with an interruption.

4. Spikes notify once per incident

When an issue's rate breaks from baseline (the exact definition is next), the incident opens and sends one notification. The issue then holds a "spiking" state and sends nothing further — no drumbeat — until the rate genuinely returns toward baseline and the incident closes. Only a fresh breach after recovery can notify again.

5. Noise never notifies

Issues classified as noise — ad-blocker blocks, browser-extension errors, requests cancelled by navigation, HTTP status 0 — never produce an email or Slack message. There is deliberately no "send me noise anyway" toggle: a team that wants alerts on a pattern Crossdeck classifies as noise should change the classification, not re-introduce the spam.

How spike detection works

A spike is a rate anomaly, never raw growth. The question is not "are there more events than yesterday?" — it's "did this issue break hard from its own baseline, in a way your project's overall traffic doesn't explain?"

Concretely, every issue maintains a rolling events-per-hour baseline (an exponential moving average of past hours). On each event, Crossdeck computes:

The normalization is the part that keeps the signal honest. On a viral day where everything 10×es together, every issue's raw ratio climbs — and the project growth factor divides it right back out. Every issue tracking smoothly upward alongside traffic is the product succeeding, not an incident. A quiet project, meanwhile, never excuses a spike: the growth factor can dampen, never amplify.

ThresholdValueWhat it does
Events-per-hour floor10Absolute minimum to open an incident. A "spike" of 9 events an hour isn't one.
Open ratio10×Normalized ratio at or above this opens the incident — the one moment that may notify.
Recovery ratioThe incident closes only when the normalized ratio drops below this. The gap between 10× and 3× is hysteresis: a rate flapping around the open threshold can't close and re-open the incident repeatedly.
Minimum incident duration1 hourAn incident can't close before this dwell time, however fast the rate recovers — flap damping on the other edge.

The incident lifecycle is a two-state machine: nonespiking on a breach (notifies, once), spikingnone on recovery (re-arms, silently). There is no third state and no path that notifies twice within one incident.

Channels

Email

Error alert emails go to the project owner's account email. All five flavours — first occurrence, regression, escalation, still-happening, and spike — share one template family, and each cites the source-map-resolved top frame when a map is available (see Source maps).

The 4-per-24h hard cap applies to first, regression, escalation, and still-happening emails. Spike notifications are exempt from the cap, deliberately: a genuine rate incident must always land, and the once-per-incident state machine is its own rate limit — an issue physically cannot produce a second spike notification until the first incident recovers.

Email alerts can be turned off project-wide in the project's notification settings (notifications.email.errorAlerts); per-issue, use Mute alerts on the issue page.

Slack

Connect Slack from Developers → Integrations. The flow is Slack OAuth: you're redirected to Slack, pick a single channel, and approve. Crossdeck requests only the incoming-webhook scope — the most restrictive scope Slack offers. It grants posting to the one channel you chose, and nothing else: Crossdeck cannot read messages, list members, or post anywhere other than that channel. One channel per project.

Once connected, Send test message fires a synthetic alert through the same code path real alerts use — if it lands, the wiring is correct. The Alert preferences panel on the same page lists every alert stream grouped by kind (customer activity, errors, pipeline health); everything is on by default, and a flipped toggle silences that stream entirely. Two keys govern errors:

Slack honors the same gates as email: muted issues, ignored issues, and noise-classified issues never post.

Custom alert rules

Everything above is about errors. Custom alert rules extend the same discipline to any event you track: "notify me when checkout_failed fires at least 10 times in 5 minutes." You define them on the Alerts page in the dashboard.

A rule has five parts:

PartWhat it doesBounds
EventThe exact event name from cd.track(name, …). Exact match — no wildcards.1–200 chars
ThresholdThe rule fires when the count of matching events in the window is at or above this number. The comparison is always ≥ — there is no operator to choose, and no fudge factor or rounding on either side.1–1,000,000
WindowA sliding window, in seconds, ending now. Evaluated exactly as configured.1 min – 24 h, default 5 min
CooldownAfter a fire, the rule stays silent for this long, however high the count climbs — the page-storm guard.1 min – 24 h, default 15 min
Property filtersOptional equality filters on event properties (e.g. plan = pro), string or number. All filters must match for an event to count. Equality only — the semantics stay simple and auditable.Up to 5

How rules evaluate

Every enabled rule is evaluated once per minute: the count query runs over both environments and excludes bot traffic, then the result is compared against the threshold. The cooldown check and the fire decision happen in a single atomic transaction on the rule itself, so two evaluator runs racing can never double-page you. Each fire is recorded in a per-rule history — the count observed, when it fired, and which channels were attempted and which actually delivered — shown on the Alerts page.

Where a fire goes

Each rule picks its own channels; at least one must be enabled.

A failed channel never blocks the others — each is attempted independently, and the fire history records exactly which ones landed.

Rules are workspace-scoped: creating, editing, or deleting one requires project membership, and every change lands in the audit log under alert_rules with the actor's identity, IP, and the rule's before/after shape.

Plain-English summaries

Every issue page carries a "What this means" card: three short blocks — What broke, Who's affected, What to do — plus a severity and confidence label, written for whoever runs the business rather than whoever reads stack traces. It loads automatically when you open an issue.

The summary explains the evidence; it doesn't guess. The model receives a fixed, redacted slice of the issue and nothing more: exception type and message, the top stack frame, event type, current priority and level, environment, event count, distinct users and paying customers affected, the last URL and runtime, first-seen time, and up to eight breadcrumb summaries that have already had emails, IP addresses, bearer tokens, and query-string secrets redacted and been clipped to 120 characters. It never sees customer IDs, IP addresses, Stripe metadata, or auth tokens, and it consults no outside sources — the answer is grounded in your project's state or it isn't given.

Two refusals are built in. Issues classified as noise don't run the model at all — they get a deterministic explanation naming the noise category, severity low, confidence high, because a model guessing about an ad-blocker block is how "Google Tag Manager failed to load" ends up labeled critical. And cross-origin script errors that carry no detail are labeled low severity, low confidence, since the browser hid the evidence.

The result is cached on the issue, so subsequent views render instantly; it regenerates only when you ask for a fresh read or the underlying error message changes. Every generation is recorded in an audit log.

Mute vs ignore

Two controls silence an issue, and they answer different questions.

Mute alertsIgnore
What it says"I know about this — stop interrupting me.""This is unactionable — I never want to hear about it."
MechanismPer-issue alertsMuted toggle on the issue page.Status set to ignored.
Issues listStays in the Open tab, fully visible.Leaves the Open tab; keeps counting under the Ignored tab.
Email + SlackNothing fires, spikes included.Nothing fires, spikes included.
Spike behaviorA genuine spike sets the quiet review chip in the dashboard — never a notification.
UndoClick the toggle again.Click Unignore — the issue returns to the Open list.

Use mute for an issue you're actively working on whose alerts you've already absorbed. Use ignore for patterns that will never be actionable — the counting continues, the interruptions stop, forever.