Chapter 03

Issue grouping

PostHog groups exceptions into issues by their code. Sometimes exceptions that look different in code have the same cause.

When a stay's host record is missing, Trip details and the stay page both throw a HostNotFoundError, from readTripHost and StayPageView. PostHog puts these exceptions in two issues Fig. 1.

IssueOccurrences

HostNotFoundError

Host "colette" not found for booking booking_3

readTripHost in src/trips/trip-details.ts

Active · Unassigned

434

HostNotFoundError

Host "colette" not found for stay stay-03

StayPageView in src/stays/StayPageView.tsx

Active · Unassigned

1,008
Fig. 1 – One missing host record opens two issues in Twig's issue list, one for each function that throws.

How PostHog groups

PostHog decides how to group exceptions into issues by going through these rules, in order:

  1. A custom fingerprint set in source code. If that's not set:
  2. A grouping rule in PostHog. If that's not set:
  3. A default fingerprint PostHog generates.

Default generated fingerprints

PostHog generates the fingerprint from the exception type and Twig's frames in the stack trace, or from the message if there's no stack trace.

Both exceptions are a HostNotFoundError, but one throws in readTripHost and the other in StayPageView Fig. 2. Different frames make different fingerprints, so PostHog puts them in two issues.

HostNotFoundError

Host "colette" not found for booking booking_3

HostNotFoundError

Host "colette" not found for stay stay-03

Fig. 2 – The same error from two places. The Twig frames differ, so the fingerprints differ.

When a source map is absent, PostHog groups by the first in-app frame in the minified code instead. Each deploy can minify that frame differently, so the same bug can land in a new issue after every deploy.

Custom grouping

The engineers want every HostNotFoundError in one issue, wherever Twig throws it. They add a grouping rule in Error Tracking settings that matches the exception type Fig. 3.

New grouping rule

Matching exceptions will be grouped as a single issue.

FiltersMatchAllAny
Exception type
= equals
HostNotFoundError
Add filter

1,442 exceptions across 2 issues would have been grouped into one issue in the last 7 days

Test ruleCancelSave
Fig. 3 – A grouping rule that puts every HostNotFoundError in one issue. Before saving, the editor tests how many exceptions it would have grouped in the last 7 days.

The rule only applies to new exceptions, so the engineers also select the two existing issues in the issue list and click Merge.

Custom fingerprinting

The grouping rule puts every HostNotFoundError in one issue. The engineers want one issue per missing host instead, because each host record needs its own fix.

With grouping rules, every host needs its own rule. Twig's code already knows which host is missing, so it's easier to put the host in the fingerprint there Fig. 4.

Inspector
SDK callcaptureException
browser tracking code
posthog.captureException(error, {  surface: context.surface,  stay_id: context.stayId,  $exception_fingerprint:    `host-not-found:${error.hostId}`,})
Fig. 4 – Twig's capture call for a missing host. The fingerprint includes the host, so each missing host gets its own issue.

A custom fingerprint wins over the grouping rule. Each missing host now gets its own issue, whether Trip details or the stay page finds it. Like grouping rules, custom fingerprints only apply to new exceptions.

Misgrouped issues

As Twig grows and the team changes, the engineers keep adjusting how they group issues.

When issues are misgrouped, Twig's engineers debug like this:

We turned this into a skill for you to debug your misgrouped issues Fig. 5.

error-tracking-grouping/SKILL.md
---
name: error-tracking-grouping
description: >
Fix a misgrouped PostHog Error Tracking issue. Find the feature the exceptions share, group on an
existing property when there is one, and add a shared property through SDK context when there
isn't.
---
# Fix misgrouped issues
Read the current docs first. Every PostHog docs URL returns Markdown if you append `.md`:
- https://posthog.com/docs/error-tracking/grouping-issues.md
- https://posthog.com/docs/error-tracking/fingerprints.md
- https://posthog.com/docs/libraries/python.md (contexts)
- https://posthog.com/docs/libraries/node.md (contexts)
- https://posthog.com/docs/libraries/js/usage.md (super properties)
Walk each issue through these checks in order, and stop at the first yes.
## 1. Are the frames unresolved?
If the frames point into minified bundles, fix source maps first. Unresolved frames change with
every deploy, so one bug keeps opening new issues.
## 2. Does the grouping look right?
One issue should be one bug that one change fixes. Compare the exception type, message, and top
in-app frames of each issue. If each issue is one bug, leave it alone.
## 3. Does one issue mix different bugs?
A grouping rule or fingerprint is too broad. Narrow it rather than adding another rule.
## 4. Does a shared property exist?
Look for a feature every exception in the group has and no exception outside it has. Check the
exception event first: `$exception_types`, `$exception_values`, `$exception_sources`, the URL, and
any custom properties. Then check the code: the service, route, job, or feature where they're
thrown.
If an existing property identifies the group, add a grouping rule in Error Tracking settings.
Filter on that property, test the match count, and place the rule before broader rules. The first
matching rule wins. No deploy needed.
Rules only apply to new exceptions, so merge the issues that already exist from the issue list.
## 5. Does the code decide each case?
If the code must pick the group for each exception, set `$exception_fingerprint` at capture. For
autocaptured exceptions, set it in `before_send`. Also set `$issue_name` and `$issue_description`.
## 6. Otherwise, add a context property
Add one property and set it once, at the layer every exception passes through. Don't add it at each
throw site.
| Where the exceptions come from | Set the property with |
| --- | --- |
| A Python request, job, or task | `with new_context(): tag("key", value)` at the entry point |
| A Node request or job | `posthog.withContext({ properties: {...} }, fn)` in middleware |
| A browser app area | `posthog.register({...})` when the area loads |
| One known call site | Properties on `captureException(error, {...})` |
Then add a grouping rule on the new property, as in step 4.
## Rules
1. Don't group to route ownership or to filter. Use assignment rules for that. A property you add
in step 6 works for both.
2. Never put a catch-all grouping rule first.
## Finally
List each issue you changed, the property you grouped on, and whether you added it. Say which
changes need a code deploy.
Fig. 5 – The skill Twig's engineers use to debug misgrouped issues. Copy it and give it to your agent.