> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Issue grouping – Error Tracking pocket guide
Canonical URL: https://posthog.com/learn/error-tracking/pocket-guide/issue-grouping
> **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.posthog.com` instead of `https://app.posthog.com` (app host for links).
## On this page
- [How PostHog groups](#how-posthog-groups)
- [Default generated fingerprints](#default-generated-fingerprints)
- [Custom grouping](#custom-grouping)
- [Custom fingerprinting](#custom-fingerprinting)
- [Misgrouped issues](#misgrouped-issues)
Chapter 03
# Issue grouping – Error Tracking pocket guide
PostHog groups exceptions into [issues](https://posthog.com/docs/error-tracking/issues-and-exceptions.md) 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.
IssueVolumeOccurrencesSessionsUsers
HostNotFoundError
Host "colette" not found for booking booking\_3
readTripHost in src/trips/trip-details.ts1
Active · Unassigned
4 minutes ago
434287251
HostNotFoundError
Host "colette" not found for stay stay-03
StayPageView in src/stays/StayPageView.tsx
Active · Unassigned
6 minutes ago
1,008612539
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](https://posthog.com/docs/error-tracking/fingerprints.md) set in source code.** If that's not set:
2. **A [grouping rule](https://posthog.com/docs/error-tracking/grouping-issues.md#option-1-custom-grouping-rule) in PostHog.** If that's not set:
3. **A default fingerprint PostHog generates.**
%%{init: {"theme":"base","fontFamily":"RoundHog, sans-serif","themeVariables":{"fontFamily":"RoundHog, sans-serif","primaryColor":"#FFDCA3","primaryBorderColor":"#FFA81C","lineColor":"#1490E8"},"themeCSS":".node rect, .node polygon { fill: rgb(var(--accent)); stroke: rgb(var(--border)); stroke-width: 1.5px; } .node.check rect, .node.check polygon { fill: #FFA81C26; stroke: #FFA81C; } .node.act rect, .node.act polygon { fill: #1490E826; stroke: #1490E8; } .nodeLabel, .edgeLabel { color: rgb(var(--text-primary)); } .flowchart-link { stroke: rgb(var(--text-secondary)) !important; } .marker { fill: rgb(var(--text-secondary)) !important; stroke: rgb(var(--text-secondary)) !important; } .edgeLabel foreignObject { overflow: visible; } span.edgeLabel { background: transparent !important; } .yes, .no { background: rgb(var(--bg)); color: rgb(var(--text-primary)); padding: 0 3px; } .no { position: relative; left: 30px; }"}}%% %%{init: {"block": {"padding": 22}}}%% block-beta columns 3 exception\["New exception"\] space space custom("Custom fingerprint in source code?") space useCustom\["Group by that fingerprint"\] rule("Grouping rule matches?") space useRule\["Group by the rule"\] generated\["Group by a generated fingerprint"\] space space exception --> custom custom -- "yes" --> useCustom custom -- "no" --> rule rule -- "yes" --> useRule rule -- "no" --> generated class custom,rule check class useCustom,useRule,generated act
## Default generated fingerprints
PostHog generates the fingerprint from the exception type and Twig's frames in the [stack trace](https://posthog.com/docs/error-tracking/stack-traces.md), 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
- readTripHostsrc/trips/trip-details.ts@12:111
- openTripDetailssrc/account/AccountPages.tsx@42:18
- renderWithHooksnode\_modules/react-dom/client.js@5654:24
HostNotFoundError
Host "colette" not found for stay stay-03
- StayPageViewsrc/stays/StayPageView.tsx@14:112
- renderWithHooksnode\_modules/react-dom/client.js@5654:24
- performUnitOfWorknode\_modules/react-dom/client.js@12561:98
Fig. 2 – The same error from two places. The Twig frames differ, so the fingerprints differ.
When a [source map](https://posthog.com/docs/error-tracking/upload-source-maps.md) 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](https://app.posthog.com/error_tracking/configuration#selectedSetting=error-tracking-custom-grouping) that matches the exception type Fig. 3.
New grouping rule
Matching exceptions will be grouped as a single issue.
FiltersMatchAllAny1
Exception type2
\= 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,1 stay_id: context.stayId,2 $exception_fingerprint: `host-not-found:${error.hostId}`,3})
```
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:
%%{init: {"theme":"base","fontFamily":"RoundHog, sans-serif","themeVariables":{"fontFamily":"RoundHog, sans-serif","primaryColor":"#FFDCA3","primaryBorderColor":"#FFA81C","lineColor":"#1490E8"},"themeCSS":".node rect, .node polygon { fill: rgb(var(--accent)); stroke: rgb(var(--border)); stroke-width: 1.5px; } .node.check rect, .node.check polygon { fill: #FFA81C26; stroke: #FFA81C; } .node.act rect, .node.act polygon { fill: #1490E826; stroke: #1490E8; } .nodeLabel, .edgeLabel { color: rgb(var(--text-primary)); } .flowchart-link { stroke: rgb(var(--text-secondary)) !important; } .marker { fill: rgb(var(--text-secondary)) !important; stroke: rgb(var(--text-secondary)) !important; } .edgeLabel foreignObject { overflow: visible; } span.edgeLabel { background: transparent !important; } .yes, .no { background: rgb(var(--bg)); color: rgb(var(--text-primary)); padding: 0 3px; } .no { position: relative; left: 30px; }"}}%% %%{init: {"block": {"padding": 22}}}%% block-beta columns 3 issue\["Misgrouped issue"\] space space frames("Frames unresolved?") space maps\["Fix source maps first"\] right("Grouping looks right?") space leave\["Leave it alone"\] mixed("Mixes different bugs?") space narrow\["Narrow the rule"\] shared("Shared property exists?") space rule\["Add a grouping rule"\] percase("Code decides each case?") space fingerprint\["Set a custom fingerprint"\] add\["Add a context property"\] space space issue --> frames frames -- "yes" --> maps frames -- "no" --> right right -- "yes" --> leave right -- "no" --> mixed mixed -- "yes" --> narrow mixed -- "no" --> shared shared -- "yes" --> rule shared -- "no" --> percase percase -- "yes" --> fingerprint percase -- "no" --> add class frames,right,mixed,shared,percase check class maps,leave,narrow,rule,fingerprint,add act
We turned this into a skill for you to debug your misgrouped issues Fig. 5.
error-tracking-grouping/SKILL.md
```markdown
---
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.
```
Show full example
Fig. 5 – The skill Twig's engineers use to debug misgrouped issues. Copy it and give it to your agent.
[Reduce noise →](https://posthog.com/learn/error-tracking/pocket-guide/reduce-noise.md)
**Explore further**
[Grouping exceptions into issues](https://posthog.com/docs/error-tracking/grouping-issues.md) · [Fingerprints](https://posthog.com/docs/error-tracking/fingerprints.md)