Claude
Skills
Sign in
Back

investigating-error-issue

Included with Lifetime
$97 forever

Investigates a single PostHog error tracking issue end-to-end. Use when the user provides an issue ID or pastes an issue URL (`/error_tracking/<id>`) and wants to understand the error — who it affects, what triggers it, when it started, whether it correlates with a release, browser, OS, or feature flag, and what the next step should be. Pulls aggregated metrics, sample exception events, segment breakdowns, linked replays, and synthesizes a hypothesis-grade summary in one pass.

Data & Analytics

What this skill does


# Investigating an error tracking issue

When a user asks "what's going on with this error?" or pastes an issue URL, gather
the context they would otherwise have to assemble manually: who is hitting it, what
changed, where it happens, and whether a replay shows the cause.

## Available tools

| Tool                                        | Purpose                                                                                     |
| ------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `posthog:query-error-tracking-issue`        | Compact issue details (status, assignee, top frame, release, aggregates)                    |
| `posthog:query-error-tracking-issue-events` | Sampled `$exception` events with stack, URL, browser, `$session_id`                         |
| `posthog:execute-sql`                       | Breakdowns, release / flag correlations, surrounding events + console logs around the error |
| `posthog:query-logs`                        | OTEL log entries around the error timestamp for server-side issues                          |
| `posthog:query-session-recordings-list`     | Linked replays (delegate ranking to `finding-replay-for-issue`)                             |
| `posthog:read-data-schema`                  | Confirm property keys before filtering on them                                              |

## Workflow

### Step 1 — Establish the issue baseline

Fetch the issue record with its compact aggregates and a sparkline:

```json
posthog:query-error-tracking-issue
{
  "issueId": "<issue_id>",
  "dateRange": { "date_from": "-30d" },
  "includeSparkline": true,
  "volumeResolution": 12
}
```

Capture: `name`, `description`, `status`, `first_seen`, `last_seen`, `assignee`,
total `occurrences` / `users` / `sessions`, top in-app frame, latest release
metadata, and the volume buckets.

The sparkline tells you the shape — flat, spike, ramp, or recurring — and that
shape drives the rest of the investigation. If the user only asked a status
question, skip `includeSparkline` to save tokens.

### Step 2 — Pull a sample exception event

A captured event has the stack frames, URL, browser, and properties needed to
reason about cause. Pull a recent sample first, then an early one to compare.

```json
posthog:query-error-tracking-issue-events
{
  "issueId": "<issue_id>",
  "limit": 1,
  "verbosity": "stack"
}
```

Use `verbosity: "raw"` only if the truncated stack hides the answer. The tool
defaults to `onlyAppFrames: true`, which strips vendor frames; flip to `false`
when the bug appears to live in a third-party library — or when the response
comes back with `stacktrace.type: "resolved"` but no frames at all (common for
minified bundles where every frame looks vendor-y to the resolver, e.g. React
production builds).

For the earliest sample, narrow `dateRange` to a tight window around the
issue's `first_seen` (e.g. set `date_from` slightly before and `date_to`
slightly after) and pass `orderDirection: "ASC"` so you get the earliest
event in the window rather than the latest — the tool defaults to `DESC`,
which would return a recent event and silently duplicate the first call.
If recent and earliest events look materially different — different stack
root, different URL pattern — the issue may be a grouping mistake. Flag for
`grouping-noisy-errors` instead of continuing as if it were one bug.

### Step 3 — Run breakdowns to isolate the cause

Breakdowns aren't a typed tool — drop into `execute-sql`. Run only the
breakdowns the issue's shape suggests; each one costs a query and clutters the
synthesis.

| Sparkline shape   | First breakdown to try                                                   |
| ----------------- | ------------------------------------------------------------------------ |
| Spike from zero   | By app version / release — almost always a deploy regression (see below) |
| Steady-state high | By browser / OS — rendering or platform-specific bug                     |
| Ramp              | By geography or feature flag — gradual rollout exposure                  |
| Bursts then quiet | By time of day or `$current_url` — scheduled job or specific page        |

#### Picking the right version property

PostHog emits three version-shaped fields. They mean different things and only
one of them answers "what version of the user's app introduced this?":

| Property              | What it is                                                | Auto-captured by                                                                   | Use for                                                                  |
| --------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `$exception_releases` | Cymbal-managed release map, keyed by release ID           | Only when SDK publishes release metadata (e.g. sourcemap upload tied to a release) | Most precise release attribution **when present**                        |
| `$app_version`        | The user's deployed app version                           | iOS (`CFBundleShortVersionString`), React Native (Expo / react-native-device-info) | "What deploy of my app introduced this?" — the question users care about |
| `$lib_version`        | The PostHog SDK library version (e.g. posthog-js 1.298.0) | Every SDK on every event                                                           | The narrow "did upgrading the PostHog SDK introduce this?" question      |

`$lib_version` is on virtually every event, which makes it tempting — but it's
the PostHog library version, not the user's app version. A constant
`$lib_version` paired with a spike means the user shipped a regression in
their own code with the SDK unchanged, which is the common case. Reach for
`$lib_version` only when nothing else is populated and you're explicitly
asking "did upgrading PostHog cause this?".

Web / server / Node / Java / Python projects do **not** auto-capture
`$app_version` — the customer has to set it (via `register`, a context
provider, or `before_send`). If the breakdown comes back with one
`$app_version` row of all-NULL, say so explicitly in the synthesis and
suggest the customer wire it up; falling back to `$exception_releases` or to
a per-day timeline by `first_seen` keeps the investigation moving.

Example (`$app_version` — populated automatically on mobile, manually on
web / server):

```sql
posthog:execute-sql
SELECT
    properties.$app_version AS app_version,
    count() AS occurrences,
    uniq(person_id) AS users,
    min(timestamp) AS first_seen,
    max(timestamp) AS last_seen
FROM events
WHERE event = '$exception'
    AND (issue_id = '<issue_id>' OR properties.$exception_issue_id = '<issue_id>')
    AND timestamp > now() - INTERVAL 30 DAY
GROUP BY app_version
ORDER BY occurrences DESC
LIMIT 20
```

The `(issue_id = ... OR properties.$exception_issue_id = ...)` pattern
mirrors the canonical `build_issue_where` clause from
`products/error_tracking/backend/api/query_utils.py`. `issue_id` is the
resolved virtual field on `events` (it follows fingerprint overrides so
merged/split issues route correctly); `properties.$exception_issue_id` is
the raw event property captured at ingestion. Filtering on only the property
silently undercounts events for issues that have been merged or split.

If `first_seen` for one `app_version` is much later than the issue's overall
`first_seen`, that release introduced or worsened the bug — strong root-cause
signal. If every row is `NULL`, the SDK isn't reporting an app version on
this project (common on web / server) — switch to `$exception_releases` if
the customer ships releases, or fall back to a `toDate(timestamp)` timeline.

When `$exception_releases` is populated, it's a JSON dict keyed by release
ID. There is no top-level `$r

Related in Data & Analytics