skills/suppressing-noisy-errors/SKILL.md
Create PostHog error tracking suppression rules to drop high-volume, low-value errors at ingestion. Use when the user asks "stop capturing this error", "drop browser extension errors", "ignore ResizeObserver loops", "suppress bot-driven errors", or wants to reduce ingestion cost from noisy unactionable errors. Identifies suppression candidates, scopes the filter tightly, decides between full suppression and sampling, and confirms the rule before creating it. Suppressed errors are dropped permanently — this skill defaults to caution.
npx skillsauth add posthog/ai-plugin suppressing-noisy-errorsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Suppression is destructive in spirit: matching events are dropped at ingestion and never become issues. The wrong rule silently throws away real bugs. This skill exists to make sure suppression is applied only to patterns that are genuinely unactionable, with filters narrow enough to avoid swallowing unrelated errors.
Suppression is the right tool when an error is:
ResizeObserver loop limit exceeded,
Script error., Non-Error promise rejection captured with empty payloads.sampling_rate instead of full suppression so you keep visibility without
paying full cost.Suppression is not the right tool when:
archived or resolved instead so it surfaces if it returns.grouping-noisy-errors).| Tool | Purpose |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| posthog:query-error-tracking-issues-list | Find suppression candidates by volume and impact; dry-run a candidate filter via filterGroup |
| posthog:query-error-tracking-issue-events | Inspect sampled $exception events to confirm the pattern |
| posthog:execute-sql | Fallback dry-run for filters that need OR groups or operators outside the filterGroup allowed list |
| posthog:error-tracking-suppression-rules-list | Check existing suppression rules |
| posthog:error-tracking-suppression-rules-create | Create the suppression rule |
| posthog:error-tracking-issues-partial-update | Hide past data via issue status without dropping events at ingestion |
High occurrences with low distinct users is the strongest noise signal — one user (or one bot) producing many events.
posthog:query-error-tracking-issues-list
{
"status": "active",
"orderBy": "occurrences",
"orderDirection": "DESC",
"dateRange": { "date_from": "-7d" },
"limit": 30,
"volumeResolution": 0
}
Look for:
occurrences, low users ratio (e.g., 50,000 occurrences, 3 users → likely
bot or extension loop)ResizeObserver loop,
Script error., extension namespaces (chrome-extension://,
moz-extension://, safari-extension://)For each candidate, pull a sample of $exception events and check that the
pattern matches what you intend to suppress:
posthog:query-error-tracking-issue-events
{
"issueId": "<candidate_issue_id>",
"limit": 10,
"verbosity": "stack"
}
onlyAppFrames defaults to true, but for noise investigation you usually
want the third-party frames visible — pass onlyAppFrames: false so extension
URLs and vendor domains show up in the stack.
Confirm:
If any sample doesn't match, narrow the filter or skip the candidate.
Suppression rules are configured with the same filter shape as grouping rules.
The error-tracking-suppression-rules-create tool description warns explicitly:
do not create match-all rules and do not create overly broad rules.
Match on the most specific property combination you can:
| Noise pattern | Recommended filter |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Chrome extension errors | $exception_sources icontains "chrome-extension://" |
| Firefox extension errors | $exception_sources icontains "moz-extension://" |
| Safari extension errors | $exception_sources icontains "safari-extension://" |
| ResizeObserver loop | $exception_values icontains "ResizeObserver loop" (the message is specific; a type filter is optional) |
| Cross-origin "Script error." | $exception_values icontains "Script error." AND $exception_types exact "Error" |
| Bot user agents | $raw_user_agent regex "(?i)bot" for a single term; see the alternation pattern below for matching several bot/crawler markers in one rule |
| Third-party network beacon failures | $exception_sources icontains "<vendor-domain>" AND a type filter (e.g. $exception_types exact "TypeError") |
The canonical exception properties ($exception_types, $exception_values,
$exception_sources, $exception_functions) are arrays at capture time. The
property filter compiler special-cases them — it parses the
JSON-materialized column and wraps the filter in
arrayExists(v -> ..., JSONExtract(...)), so all the standard operators
(exact, is_not, icontains, not_icontains, regex, not_regex) work
against individual elements with the bare value: exact "TypeError", not
exact '["TypeError"]' or regex '"TypeError"'.
The singular forms ($exception_type, $exception_message) and
$exception_stack_trace_raw are emitted on a fraction of a percent of events;
filtering on them produces a rule that silently never matches.
Note that the regex operator on suppression and grouping rules compiles to
the HogVM Operation::Regex, which is case-sensitive. Use the (?i)
inline flag for case-insensitive matching (e.g. (?i)headlesschrome).
For matching multiple bot or crawler terms, use bare pipes for alternation.
Pass this as the value field of the regex filter when calling the API
($raw_user_agent is more reliable than the parsed $user_agent, which some
parsers normalize away from crawler markers):
(?i)(HeadlessChrome|bot|crawler|spider)
Whenever possible, AND together two or more conditions — type plus message, or message plus URL pattern — so the rule is specific to the real noise.
If you want to keep some visibility, use sampling_rate between 0 and 1:
sampling_rate: 1 — drop everything matching (full suppression)sampling_rate: 0.95 — drop 95% of matching events, keep 5% as sentinel datasampling_rate: 0.5 — half-rate, useful for high-volume but real errorsDefault to a non-1.0 sampling rate when there's any doubt that the pattern is purely noise. You can tighten to 1.0 later once the data shows the rule isn't catching real issues.
Before asking for confirmation, run the candidate filter against the issues
list so you (and the user) can see exactly which issues the rule would have
caught over the last 7 days. query-error-tracking-issues-list accepts the
same property-filter shape suppression rules use via its filterGroup
parameter, so for a typical AND-only rule you can pass the rule's leaf
filters directly — no HogQL translation needed:
posthog:query-error-tracking-issues-list
{
"filterGroup": [
{ "type": "event", "key": "$exception_types", "operator": "exact", "value": "Error" },
{ "type": "event", "key": "$exception_values", "operator": "icontains", "value": "ResizeObserver loop" }
],
"dateRange": { "date_from": "-7d" },
"status": "all",
"filterTestAccounts": false,
"orderBy": "occurrences",
"limit": 25
}
Important defaults to override for suppression preview:
status: "all" — suppression applies regardless of issue status, so don't
let the default active filter hide already-archived noise.filterTestAccounts: false — the rule will not respect the test-account
toggle at ingestion. The preview should match production reality.Each row is one issue the rule would catch: name (exception type),
description (sample message), source, library, plus
aggregations.occurrences and aggregations.users. The issue list is
the per-issue breakdown — read every row.
The single most important safety check: scan the result for any issue
whose name / description / source looks like a real bug the team
would want to fix, not noise. A filter that looks tight by message text
will routinely match unrelated issues that happen to share a phrase, and
this is the failure mode that silently destroys real data once the rule is
live. If you see anything suspicious, narrow the filter (step 3) and rerun
this step until only the genuine noise pattern is in the list.
Add up aggregations.occurrences and aggregations.users across rows for
the blast-radius totals you'll surface to the user in step 6. If you need
exact totals across more than limit issues, paginate with offset or
fall back to the HogQL aggregate at the end of this step.
For one or two concrete sample events with full stack traces, follow up on
the most suspicious-looking issue with query-error-tracking-issue-events:
posthog:query-error-tracking-issue-events
{
"issueId": "<id from the list>",
"limit": 3,
"verbosity": "stack",
"onlyAppFrames": false
}
filterGroup is flat AND only. Drop into HogQL when:
type: "OR" at the outer group or any nested OR.filterGroup (e.g. between,
in, semver_*).The HogQL shape mirrors what the suppression rule bytecode compiles to.
The materialized property column is nullable, so the coalesce(..., '[]')
wrapper is required — without it ClickHouse rejects the query with
"Nested type Array(String) cannot be inside Nullable type":
SELECT
count() AS matched,
count(DISTINCT distinct_id) AS users,
count(DISTINCT properties.$exception_issue_id) AS issues
FROM events
WHERE event = '$exception'
AND timestamp > now() - INTERVAL 7 DAY
AND arrayExists(
v -> ifNull(ilike(v, '<pattern>'), 0),
JSONExtract(coalesce(properties.$exception_values, '[]'), 'Array(String)')
)
Use ilike for icontains, plain equality for exact, match(v, '<pattern>') for regex. The rule's regex is case-sensitive — add
(?i) inline if needed.
Suppression is destructive in spirit even though the API marks it
destructive: false. Show the user before creating:
occurrences and users,
plus the aggregate totals — call out any rows that look like real bugsposthog:error-tracking-suppression-rules-list first)Wait for explicit confirmation. Then create:
posthog:error-tracking-suppression-rules-create
{
"filters": {
"type": "AND",
"values": [
{
"type": "event",
"key": "$exception_types",
"operator": "exact",
"value": "Error"
},
{
"type": "event",
"key": "$exception_values",
"operator": "icontains",
"value": "ResizeObserver loop"
}
]
},
"sampling_rate": 0.95
}
Start at 0.95 (drop 95%, keep 5% as sentinel data) so you can confirm the
rule isn't catching real errors before tightening to 1.0.
After creating the rule:
WHERE timestamp > now() - INTERVAL 1 HOUR once an hour has passed).
Don't re-run the 7-day estimate from step 5 — suppression only applies to
new events, so historical events in the window will still be there and the
count won't drop.error-tracking-suppression-rules-partial-update,
-destroy) are not enabled — the agent has no way to recover programmatically.If you see signs of false positives (a real issue going quiet at the same time the rule was created), prefer disabling the rule over deleting it — that preserves the rule's configuration for forensic review.
error-tracking-issues-partial-update with status: "suppressed"
over a suppression rule.filters empty.testing
Focused Signals scout for PostHog projects running surveys. Watches active surveys for score regressions (NPS / CSAT / rating drops), response-volume drops, abandonment spikes, and targeting drift, AND aggregates open-text responses into recurring themes the team should know about (clusters of complaints, praise, feature requests). Emits findings only when a theme or anomaly clears the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills. Picked uniformly at random by the coordinator alongside `signals-scout-general` and other specialists.
development
Focused Signals scout for PostHog projects using revenue analytics. Watches the derived revenue product for upstream failures (Stripe sync stalls, capture regressions), config drift (missing subscription property, currency mix surprises, broken Stripe↔person joins, deferred-revenue gaps), and goal-miss escalations. Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills. Picked uniformly at random by the coordinator alongside `signals-scout-general` and other specialists.
testing
Focused Signals scout for finding observability gaps in PostHog itself — significant event volumes the team isn't tracking, custom events with no insight or dashboard coverage, insights pointing at events that have stopped firing, dashboards missing related context, critical events with no alerts. Watches the event-stream-vs-saved- inventory delta as the team's product evolves and emits findings recommending new insights, dashboard additions, or alerts when gaps clear the confidence bar. Self-contained peer in the signals-scout-* fleet — picked uniformly at random by the coordinator alongside `signals-scout-general` and other specialists.
testing
Focused Signals scout for PostHog projects using logs. Watches for volume bursts, severity-distribution shifts, service silence, fresh message patterns, and trace-correlated bursts via the logs ingestion pipeline. Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills. Picked uniformly at random by the coordinator alongside `signals-scout-general` and other specialists.