posthog/posthog-inbox-exploration
> Explore PostHog's Inbox and act on what it surfaces — the place where signal reports cluster into actionable issues and trends. Use when the user asks "what's in my inbox?", "what should I look at?", "which reports are actionable?", "what's PostHog flagged recently?", asks about a specific report by ID or title, wants to act on / fix / implement a report (turn it into a PR), wants to resolve, dismiss, or snooze a report, or wants to see which signal sources are configured. Covers listing, filtering, drilling into, and acting on reports, plus pointers to the deeper `signals` skill when raw signals or semantic search are needed.
This is a copy. The original lives at posthog/ai-plugin-inbox-exploration.
npx skills add https://github.com/PostHog/posthog --skill inbox-exploration
The Inbox is where PostHog surfaces signal reports — clusters of related observations
(signals) that have been aggregated into a single issue or trend (e.g. "Error rate spiked 3× on
/checkout"). Reports come from multiple source products: error tracking, session replay, web
analytics, experiments, and integrations like Linear, GitHub, and Zendesk.
Inbox is part of PostHog Desktop, PostHog's agentic surface for
engineering teams.
Don't assume the user's project has reports, or that any signal sources are configured — plenty
of projects don't have Inbox set up. Always run the setup-check workflow below before answering
the user's actual question.
_Workflow: act on an actionable report_
_Workflow: resolve, dismiss, or snooze a report_
For deeper investigation, hand off to other skills and tools:
signals skill — query document_embeddings via HogQL for raw signal text, semanticsearch across signals, or to inspect every signal that contributed to a report.
session, person, or time range, reach for the matching domain tool to pull richer context:
query-error-tracking-issues-list, query-error-tracking-issue,query-error-tracking-issue-events for error-tracking-sourced reports
query-logs, logs-count-ranges to find log activity around the issuequery-session-recordings-list, session-recording-get to findrecordings of affected users
persons-retrieve, advanced-activity-logs-list to inspect a specific user'sbehavior
query-trends, execute-sql for ad-hoc verification queriesA signal report tells you _what_ PostHog clustered. The product-specific tools tell you the
_underlying detail_ — pair them when the user wants to dig in.
| Tool | Purpose |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| inbox-reports-list | Paginated list of reports with filters (status, search, etc.) |
| inbox-reports-retrieve | Full detail for a single report |
| inbox-report-artefacts-list | A report's full work log — signal_finding evidence, status judgments, commits, task runs, notes (read-only) |
| inbox-report-artefacts-retrieve | Full detail for a single artefact (read-only) |
| inbox-reports-set-state | Resolve (resolved), dismiss (suppressed), or snooze (potential) a single report |
| inbox-reports-bulk-set-state | Same transition for 1–100 reports in one call (per-id result) |
| inbox-source-configs-list | Configured signal sources (which products feed the inbox) |
| inbox-source-configs-retrieve | Full record for a single source config |
| inbox-source-configs-partial-update | Toggle a source's enabled flag (or adjust its config) |
| posthog:execute-sql (signals skill) | HogQL access to underlying signals (read the signals skill first) |
The inbox-reports-*-list / -retrieve, inbox-report-artefacts-list / -retrieve, and
inbox-source-configs-*-list / -retrieve tools are read-only. The exposed writes are inbox-reports-set-state (resolve / dismiss / snooze a single report),
inbox-reports-bulk-set-state (the same transition for 1–100 reports in one call) — see
_Workflow: resolve, dismiss, or snooze a report_ — and inbox-source-configs-partial-update, which flips a
source's enabled flag on or off (e.g. {enabled: false} to stop a source feeding the inbox);
-create / -update exist too for standing a source up or replacing it wholesale. Other writes
(pause processing, set implementation_pr_url) are not exposed via MCP today — the PR link is
populated on the product surface when a PR is opened against a report.
What each report status means (in roughly the order a triage agent should care about):
ready — judgment finished, actionable assessment availablepending_input — waiting on user input to proceedin_progress — actively being summarized / judgedcandidate / potential — accumulated signals but not yet promoted to a real reportfailed — processing erroredsuppressed — manually hidden; not surfaced by defaultresolved — the work the report asked for is done. Terminal: a resolved report never re-promotes,so a recurrence starts a fresh report linked back to it. Set automatically when a linked
implementation PR merges, or directly via inbox-reports-set-state (see the workflow below)
By default inbox-reports-list excludes suppressed reports and orders results by
-is_suggested_reviewer,status,-updated_at — the user's own suggested reports first, then by
status, then most recently updated. Refer to the tool's input schema for filter mechanics.
is_suggested_reviewer: true on a report means **the current PostHog user is one of up to
three people the report-research flow flagged as best-placed to act on this report**. It is
the strongest signal you have that a report matters to the user _personally_, and you should
lean on it when triaging.
How the flag is produced:
underlying signals (e.g. commits that touched the failing code path).
heavily, and keeps the top three GitHub logins. These get persisted as a
SUGGESTED_REVIEWERS artefact on the report.
linked GitHub identity (social auth or GitHub integration). If the _current_ viewer's
linked GitHub login is one of them, is_suggested_reviewer flips to true for that
report.
Practical implications for triage:
true value means "you wrote (or recently touched) the code this report is about" — not"you were assigned this." It's heuristic, not authoritative.
false value doesn't mean the report is irrelevant — it can mean (a) someone else ownsthe code, (b) no one in the org has a linked GitHub account matching the suggested logins,
or (c) the source material wasn't tied to a specific repo / commits.
is_suggested_reviewer: truereports — these are the ones where the user's name is on the relevant code. Mention the
rest as a secondary group rather than mixing them in.
("nothing in the inbox is tied to code you've authored recently") rather than pretending
the top of the list is personalized.
Run this check whenever a user asks about the inbox for the first time in a session, or any
time inbox-reports-list returns count: 0. The diagnosis decides what to say next.
inbox-source-configs-list
{ "limit": 50 }
Three meaningful cases:
Case A — no source configs at all (count: 0)
The user hasn't onboarded to Inbox / signals. Don't pretend the inbox has data. Tell the user
plainly that Inbox needs signal sources to be set up first, and that the recommended way to do
this is to install PostHog Desktop at <https://posthog.com/code>. Example response:
> Your project doesn't have any signal sources configured yet, so the Inbox is empty. Inbox surfaces
> issues and trends that PostHog automatically clusters from sources like error tracking, session
> replay, GitHub, Linear, and Zendesk. The fastest way to set this up is to install
> PostHog Desktop — once it's connected, signals will start flowing in
> and reports will appear in your inbox over the next day or so.
Stop here unless the user wants to discuss setup. Don't run further inbox tools — they'll all be
empty.
Case B — source configs exist but all are enabled: false
Sources have been set up at some point but are currently turned off. Tell the user no signals are
flowing right now. You can re-enable a source directly with
inbox-source-configs-partial-update { "id": "<source_config_uuid>", "enabled": true } (confirm
with the user first), or they can flip it on from the project's signals settings. Don't go fishing
for reports — anything still there is stale.
Case C — at least one source config is enabled: true
Setup looks healthy. If inbox-reports-list still returns nothing, it's most likely "give it time"
— signals are flowing but nothing has clustered into a report yet. Tell the user that, briefly
list which sources are active (e.g. "you have GitHub and error tracking enabled"), and offer to
check back later or to drop into the signals skill to look at raw signal volume.
If any source config has status: "failed", surface that as part of your reply — that source
isn't producing signals right now, which may explain a thin inbox.
If Step 1 found a healthy setup and at least one report exists, continue with the triage / drill /
filter workflows below.
When the user asks "what should I look at?" or "what's actionable?":
inbox-reports-list
{
"status": "ready,in_progress,pending_input",
"limit": 20
}
If count: 0 comes back, jump to the empty/unconfigured workflow above before saying "your
inbox is empty" — the right reply depends on whether sources are configured.
For each report, the response includes:
id, title, summarystatus, priority, actionability (note: null for reports still in pending_input /candidate — judgment hasn't run yet)
signal_count, total_weight — how much underlying evidence drove the reportsource_products — which product(s) the underlying signals came fromis_suggested_reviewer — whether the current user is a suggested reviewer for thisreport (see "What 'suggested reviewer' means" above — it's based on GitHub commit
authorship of the relevant code, mapped to PostHog users via linked GitHub identity)
implementation_pr_url — if a PR has been opened against this report_posthogUrl — clickable deep-link to the report; always include this in your responseGroup the results so the user can scan quickly. **Lead with reports where
is_suggested_reviewer: true** — those are the ones tied to code the current user has
authored — and only then fall back to priority groupings for the rest:
## Inbox — 8 actionable reports
⭐ Suggested for you (1)
- Checkout error rate spiked 3× — error_tracking, 47 signals (you're a suggested reviewer)
<_posthogUrl>
🔴 High priority (2 more)
- Session replays on /pricing show repeated rage clicks — session_replay, 12 signals
<_posthogUrl>
…
🟠 Medium priority (4)
…
If no reports come back with is_suggested_reviewer: true, say so explicitly before listing
the rest — don't silently drop the section.
End with a clear hand-off: "Want me to dig into the checkout errors?" → call
inbox-reports-retrieve for the full report, then optionally hop to the signals skill to look
at the underlying signal text.
When the user pastes an Inbox URL or report ID:
inbox-reports-retrieve
{ "id": "<report_uuid>" }
Returns the full record including signals_at_run and artefact_count. Then read the report's
work log:
inbox-report-artefacts-list
{ "report_id": "<report_uuid>" }
This returns the report's evidence (signal_finding), the judgments behind its
status/priority/actionability (safety_judgment, actionability_judgment, priority_judgment,
repo_selection, suggested_reviewers), and its work-log (commit, task_run, note) — the
curated "why it exists and what's been done" view, in one read-only call.
Use the signals skill only when you need the raw signal text beyond the curated findings:
inbox-reports-retrieve to get the report metadata + idinbox-report-artefacts-list for the curated evidence, judgments, and work-logsignals skill's Example 2 (fetch all signals for a specific report) — pass thereport ID as metadata.report_id in the HogQL query — only for the raw underlying signal text
The layers complement each other: inbox-report-artefacts-list gives you the curated/judged view
(evidence + judgments + PR/task-run history), and the signals skill lets you inspect the raw
observations that produced it.
When the user wants to _do_ something about a report — "fix this inbox item", "turn this into a
PR", "implement this" — not just read it. A ready report with
actionability: immediately_actionable is the usual candidate. The discipline that matters here:
**a report is a diagnosis, not ground truth — verify it against the actual code before you
implement.** Reports from signals_scout (and any LLM-research source) are especially worth
double-checking; their summary often reads as a confident root-cause with file and function
names, but it can be stale or wrong.
inbox-reports-retrieve
{ "id": "<report_uuid>" }
Before doing any work, look at:
already_addressed — if true, the fix may already be in flight or merged; confirm with theuser before duplicating it.
implementation_pr_url — if a PR is already linked, surface it instead of opening a second one.status — only ready reports carry a finished judgment. A candidate / pending_inputreport hasn't been researched yet; don't implement off a half-formed summary.
Start by reading the report's work log — its evidence and the judgments behind it:
inbox-report-artefacts-list
{ "report_id": "<report_uuid>" }
This surfaces the signal_finding evidence, the status/priority/actionability judgments, and any
commit / task_run history — exactly the "why it exists and what's already been done" you need
before touching code. Then the report's summary will name files, functions, and sometimes line
numbers. Open them and confirm the claim holds — that the cited code exists, still looks the
way the report describes, and actually produces the described failure. As a deeper fallback, pull
the raw underlying signals via the signals skill (metadata.report_id) if you need the signal
text behind the curated findings. If the diagnosis doesn't hold up, say so and stop — a wrong
report is itself a useful finding (and a candidate for _dismiss_ below), not a license to write a
speculative fix.
source_products includes signals_scout and the root cause is in a **scout's ownbehavior** (the prompt it runs, a threshold it uses), the better fix is often the scout's
SKILL.md, not the harness. Note that per-team custom scouts live in the user's Skills Store,
not this repo, so the fix site may be out of reach of a repo PR — flag that to the user.
CLAUDE.md,area-specific skills), make the change minimal, and add a regression test that would have caught
the reported failure.
Open the PR following the repo's PR conventions. There is no MCP tool to set
implementation_pr_url — that link is populated on the product surface when a PR is opened
against the report. So reference the report in the PR description (its _posthogUrl) and tell the
user which report the PR addresses, so the loop is traceable.
Don't resolve a report because you opened a PR. When the fix ships as a PR, the merge is what
resolves the report — the tasks GitHub webhook does it automatically. Resolving by hand at PR-open
time asserts work that hasn't landed, and a reviewer looking at the inbox can't tell the difference.
Manual resolve is for fixes a PR merge will never cover — a skill-body change, a config change, a
NO_REPO report — see the workflow below.
Three outcomes, one tool. Pick by what actually happened to the underlying issue:
| The issue is… | State | Why |
| --------------------------------------------- | ------------ | -------------------------------------------------- |
| fixed by work you did | resolved | terminal; the report has served its purpose |
| not real, or not worth fixing | suppressed | dismissed from the inbox, with the reason recorded |
| real but deferred, or fixed by something else | potential | back into the pipeline; reappears if it recurs |
inbox-reports-set-state
{
"id": "<report_uuid>",
"state": "suppressed",
"dismissal_reason": "analysis_wrong",
"dismissal_note": "Verified against products/foo/bar.py — the cited code path can't reach this state."
}
state: "resolved" marks the requested work done, and is terminal — a recurrence starts a freshreport rather than reopening this one. Allowed from ready / pending_input, or from a
suppressed report that held one of those when archived; anything else returns 409. **Only
resolve work that has actually landed.** A fix shipping as a PR resolves itself on merge (see
_Step 4_); resolve by hand only where the webhook can never reach — a skill-body edit, a config
change, a NO_REPO report. Don't use already_fixed + state: "potential" when _you_ did the
fixing: that pairing means "fixed by something else, might recur", so the report comes back.
state: "suppressed" dismisses the report from the inbox; state: "potential" snoozes it backinto the pipeline. When snoozing, snooze_for: <N> holds it until it accumulates N more signals.
dismissal_reason must be one of six server-validated canonical codes — already_fixed,report_unclear, analysis_wrong, wontfix_intentional, wontfix_irrelevant, other — an
unlisted value returns 400. Reach for other plus a dismissal_note for anything that doesn't
fit a specific code. dismissal_note is free-form (≤ 4000 chars). Both persist as a DISMISSAL
artefact, so the rationale survives later transitions — always include them, on a resolve too,
so a future reader knows _why_.
dismissal_note is also forwarded as a steering note to thescout that filed the report, which every scout run reads at cold start, so what you write there is
what stops the same report being filed again. Write it for that reader: name the evidence that
settles it, not just the verdict. A resolve is not forwarded, since it says the report did its job
rather than that filing it was wrong; that note stays on the report. Forwarding needs the same
skill-editing access as leaving a scout note by hand, so on a project where you lack it the note
still lands on the report but does not reach the scout.
409 if it isn't allowed from thereport's current status (and 400 if dismissal_reason isn't a canonical code). Confirm with
the user before suppressing, and capture _why_ in the note — a dismissal with no rationale is
worse than none. A report you dismissed because the diagnosis was wrong (Step 2 above) is the
textbook case: suppress it with analysis_wrong and the evidence in the note. A refunded report
is frozen: snooze and resolve both come back 409 / skipped with an explanatory detail.
inbox-reports-bulk-set-state with an idsarray (1–100). It applies the same state / dismissal_reason / dismissal_note / snooze_for
to every id and returns a per-id results list (in request order) plus a
transitioned_count / skipped_count / failed_count / not_found_count summary. Each id is
processed independently, so the call returns 200 even on partial failure — an id whose
transition isn't allowed comes back as skipped (the single-report 409) while the rest go
through. Inspect the per-id outcomes rather than assuming the whole batch succeeded.
"Are there any reports about <topic>?" — start with search:
inbox-reports-list
{
"search": "checkout",
"status": "ready,in_progress,pending_input",
"limit": 20
}
search matches title and summary. If the user is asking about a product area rather than a
keyword, use source_product:
inbox-reports-list
{
"source_product": "session_replay,error_tracking",
"limit": 20
}
If the keyword search returns nothing meaningful, hand off to the signals skill — semantic
search over signal text via embedText() will catch reports the keyword filter missed.
When the user asks "which signal sources are set up?" or "is <product> hooked up?":
inbox-source-configs-list
{ "limit": 50 }
Each entry returns id, source_product, source_type, enabled, status, plus timestamps.
For full details (including the per-source config JSON — recording filters, evaluation IDs,
etc.):
inbox-source-configs-retrieve
{ "id": "<source_config_uuid>" }
Integration credentials live in a separate Integration model — they are not in the
config blob, so it's safe to summarize the contents back to the user.
The status field reflects the underlying data import or workflow:
running / completed — feeding signals normallyfailed — the source isn't currently producing signals; flag this to the userTo turn a source on or off, use inbox-source-configs-partial-update with the config's id and
{ "enabled": true | false } — only the fields you pass change, so this is the right tool for a
plain toggle (-update replaces the whole record; -create stands up a new source). Confirm with
the user before flipping a source, since enabling one drives signal processing and spend.
inbox-source-configs-partial-update
{ "id": "<source_config_uuid>", "enabled": false }
inbox-reports-list returns count: 0,call inbox-source-configs-list first — no sources means the user needs to install
PostHog Desktop to start receiving signals; sources-but-no-reports
means signals are flowing but nothing has clustered yet
_posthogUrl so the user can click through to the reportasked
priority and actionability are null for reports still in pending_input or candidatestatus; this is expected, not a bug — judgment hasn't run yet
suppressed reports are excluded by default; pass status: "suppressed" explicitly if theuser wants to see hidden items
inbox-reports-set-state (resolve / dismiss / snooze onereport), inbox-reports-bulk-set-state (the same for 1–100 reports), and
inbox-source-configs-partial-update (toggle a source's enabled flag). To _act_ on a report
(implement a fix), verify the diagnosis against the code first, then open a PR — see
_Workflow: act on an actionable report_. A PR-backed fix is resolved automatically when the PR
merges, so don't resolve it by hand at PR-open time; setting implementation_pr_url happens on
the product surface, not via MCP. Always also surface the _posthogUrl deep-link
summary. Reports — especiallysignals_scout ones — are LLM diagnoses; confirm the cited files / functions / behavior in the
actual code before writing a fix. A report that doesn't hold up is a dismissal candidate, not a
fix
drop into the signals skill — the report layer hides individual observations; you need
HogQL on document_embeddings to see them
inbox-source-configs-retrieve returns no _posthogUrl. Don't confuse them with reports
Take posthog/posthog-inbox-exploration from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.