feat(slack_app): capture analytics event on @PostHog mention#61338
Conversation
Adds a `posthog code slack mention received` product-analytics event captured whenever the @PostHog Slack bot is mentioned and routed to the coding-agent workflow. The Slack thread is treated as the session: `thread_ts` identifies it, and a mention whose `thread_ts` is absent or equal to its own `ts` is the session's first message. The event carries `is_first_message_in_session`, `session_message_count` (1 for the first message; otherwise a best-effort count of thread replies), `slack_session_id`, and Slack context (team/channel/thread/user). The acting Slack user is resolved to a PostHog `User` so the event is attributed to a real person via their `distinct_id` (with `$set` analytics metadata); when no PostHog user can be resolved it falls back to a stable `slack:<team>:<user>` distinct id. Capture is best-effort and wrapped so analytics can never break Slack event handling. Generated-By: PostHog Code Task-Id: 65cd121b-ce0a-4fb1-be75-b14924d0f938
|
Hey @oliverb123! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
Satisfies ruff isort ordering for the new imports and restructures the distinct_id resolution so it narrows cleanly to str for mypy. Generated-By: PostHog Code Task-Id: 65cd121b-ce0a-4fb1-be75-b14924d0f938
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
products/slack_app/backend/api.py:1667-1671
The `identified and posthog_user is not None` condition is redundant. `identified` is computed as `bool(posthog_user and posthog_user.distinct_id)`, which already guarantees `posthog_user` is not None when `identified` is True. The extra guard cannot trigger in practice.
```suggestion
distinct_id = (
posthog_user.distinct_id
if identified
else f"slack:{slack_team_id}:{slack_user_id or 'unknown'}"
)
```
### Issue 2 of 3
products/slack_app/backend/api.py:1634
**Synchronous Slack API call in the webhook handler**
`conversations_replies` makes a real HTTP roundtrip on every follow-up mention, adding its latency to the time before the handler returns 202. Slack's 3-second retry window is already shared with Temporal workflow submission and user-info resolution; a slow or rate-limited `conversations_replies` response narrows that window further. Since `session_message_count` is a best-effort metric, consider moving this call outside the request path (e.g., deferring it into the analytics properties assembled by the Temporal workflow) or accepting `None` as the count for follow-ups when latency budget is tight.
### Issue 3 of 3
products/slack_app/backend/tests/test_mention_analytics.py:1-99
**Tests not parameterised**
The codebase prefers parameterised tests. The four methods here share the same `_report_slack_mention_received` entry point and differ only in event shape and mock return values. Scenarios like first-message vs. follow-up (`is_first_message_in_session`, `session_message_count`) and resolved vs. unresolved user (`distinct_id`, `posthog_user_identified`) are natural axes for `@pytest.mark.parametrize`, which would also make it easy to add the missing case where `thread_ts == ts` (thread root is the mention itself) without adding another full test method.
Reviews (1): Last reviewed commit: "chore(slack_app): fix import order and n..." | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
Adds server-side product analytics for actionable app_mention events handled by the PostHog Code Slack app, so mentions can be measured (session/thread semantics, first vs follow-up, user attribution) without affecting Slack event handling.
Changes:
- Captures a new
posthog code slack mention receivedevent at the_start_mention_workflowchokepoint, including session/thread identifiers and best-effort thread message counts. - Resolves the acting Slack user to a PostHog
Userwhen possible and falls back to a stable Slack-deriveddistinct_idwhen not. - Adds unit tests covering first-message detection, follow-up counting, fallback distinct id behavior, and best-effort failure swallowing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
products/slack_app/backend/api.py |
Adds analytics capture helper(s) and hooks capture into mention workflow start. |
products/slack_app/backend/tests/test_mention_analytics.py |
Adds unit tests for the new analytics capture behavior and failure handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Pass send_feature_flags=True to posthoganalytics.capture so the acting user's active feature flags are recorded on the mention event, matching the convention in posthog/event_usage.py report_user_action. Module-level capture is correct here since this runs in the synchronous Slack webhook view, not a Celery task (where ph_scoped_capture would be required). Generated-By: PostHog Code Task-Id: 65cd121b-ce0a-4fb1-be75-b14924d0f938
Addresses trusted code-review feedback (Greptile, Copilot): - Bound the Slack client timeout when counting thread messages so the best-effort count cannot eat into Slack's webhook retry window on a slow or rate-limited response. - Add integration/channel/thread context to both best-effort warning logs so failures are debuggable without a stack trace. - Validate the Slack user id with an isinstance check, consistent with the channel/ts handling, instead of stringifying arbitrary values. - Parameterize the analytics tests and add the case where the thread root is the mention itself (thread_ts == ts). Generated-By: PostHog Code Task-Id: 65cd121b-ce0a-4fb1-be75-b14924d0f938
|
⏭️ Skipped snapshot commit because branch advanced to The new commit will trigger its own snapshot update workflow. If you expected this workflow to succeed: This can happen due to concurrent commits. To get a fresh workflow run, either:
|
Query snapshots: Backend query snapshots updatedChanges: 1 snapshots (1 modified, 0 added, 0 deleted) What this means:
Next steps:
|
| return ROUTE_HANDLED_LOCALLY | ||
|
|
||
|
|
||
| def _count_session_thread_messages(integration: Integration, channel: str | None, thread_ts: str | None) -> int | None: |
There was a problem hiding this comment.
nit: can we move these out of the api file
Problem
We have no product-analytics event for when someone interacts with the @PostHog Slack bot. The bot handles Slack
app_mentionevents inproducts/slack_app/backend/api.py, but that path only emits structlog application logs — nothing is captured into PostHog. So questions like "how often is the bot mentioned?", "how many are first-touch vs. follow-ups in a thread?", and "who is using it?" can't be answered from events today.Changes
Adds a
posthog code slack mention receivedevent, captured at the single chokepoint where a mention is confirmed actionable and routed to the coding-agent workflow (_start_mention_workflow).The Slack thread is the session:
slack_session_id=<team>:<channel>:<thread_ts>is_first_message_in_session— true when the mention has nothread_tsor itsthread_tsequals its ownts(i.e. it opens the thread)session_message_count—1for a first message; otherwise a best-effort count of thread replies viaconversations_repliesUser, sodistinct_idis the real person's distinct id (with$setanalytics metadata, andgroupsfor org/project), per PostHog's server-side capture convention. When no PostHog user can be resolved, it falls back to a stableslack:<team>:<user>distinct id and setsposthog_user_identified=false.Capture is fully wrapped in try/except — analytics is best-effort and can never break Slack event handling.
How did you test this code?
I'm an agent (PostHog Code). I added unit tests in
products/slack_app/backend/tests/test_mention_analytics.pycovering: first-message identification + count, follow-up thread counting, the unresolved-user fallback distinct id, and that a capture failure is swallowed.I could not run the test suite in this environment — it has no
flox/hogli, nopytest/djangoinstalled, and constrained disk. I verified both files parse (ast.parse) and that the change matches the existing patterns in the file (_resolve_posthog_user_from_event,groups()fromposthog.event_usage, the structlog/best-effort idiom). A human should runhogli test products/slack_app/backend/tests/test_mention_analytics.pyand lint (ruff check/format) before merge.Automatic notifications
Docs update
No docs changes.
🤖 Agent context
Authored by PostHog Code (Claude Opus) on request to add an analytics event tracking @PostHog Slack mentions, including first-message-in-session, session message count, a session identifier, and user identification.
Key decisions:
_start_mention_workflowas the single emit point — it fires for genuinely handled mentions (after ignore/scope/project checks), including repo-picker follow-ups, rather than for every raw webhook (which includes ignored edits and unroutable events). This keeps the event meaningful and guarantees a resolved target integration is available.thread_ts) already used throughout the file as the session key, rather than inventing a new identifier.conversations_repliescall (only for follow-ups) to avoid extra Slack API cost/latency on the common first-message case._resolve_posthog_user_from_eventhelper so attribution maps to a real PostHog user where possible, matching the server-sideposthoganalytics.capture(distinct_id=user.distinct_id, ... groups=groups(...))convention.