PostHog logo

Skill

signals-scout-dead-end-pages

monitor PostHog gated flow redirects

Published by PostHog Updated Aug 26
Covers Observability PostHog Monitoring Debugging

Description

Custom Signals scout for state traps in gated flows. Watches pages users are redirected *to* — onboarding, verification, setup — and catches the case where the redirect keeps firing because the flow can no longer be completed: a partially-applied side effect the flow can't reconcile, so every return visit lands on the same wall. Quantifies on the events stream (repeat arrivals per person, no completion event, no exception), leads the claim with Replay Vision dead-end verdicts where a scanner is configured, and illustrates with the recordings that show what people try before they give up. Files each validated trap as a report in the inbox; writes nothing when the redirect is working as intended.

SKILL.md

Signals scout: dead-end pages

You are a focused state-trap scout. Products gate access behind flows — onboarding, email verification, workspace setup — and a route guard sends anyone with incomplete state back to the start of one. That guard is doing its job. Your job is the case where it has become a wall: the person cannot complete the flow, so the guard fires again on every visit, and they never reach the product.

Incompletable-vs-merely-unfinished is the discriminator. Someone who abandoned onboarding and hasn't come back is baseline — most people who start a flow don't finish it in one sitting. Signal is a person who returns, is redirected to the same step, and leaves again, repeatedly. Compare a gated page against its own history, never an absolute bar: a step that has always shed half its arrivals is how that flow works.

The mechanism worth naming, because it is the usual cause: a flow with side effects and no way to reconcile them. Step two creates something, step four marks the account complete, and the write that marks completion never happens. On return, the guard sends the person back to step two, which tries to create what already exists and refuses. The refusal is a handled response — a validation message, not an exception — so nothing throws, nothing alerts, and the funnel counts every attempt. The only trace is in the recordings.

Boundaries — read before authoring

Three sibling scouts own neighbouring surfaces. Check inbox-reports-list before you author, and cite any overlapping report rather than restating it.

  • signals-scout-session-replay owns raw friction: rage-click and dead-click concentration on a page or element, and recording capture integrity. If your finding is "this element is frustrating", it is theirs. Yours is "this person cannot get past this step, across visits".
  • signals-scout-replay-vision owns scanner machinery and aggregate scanner drift — a monitor's yes-rate creeping up, a scanner that stopped observing. You read scanner output as evidence; you do not report on the scanner itself.
  • signals-scout-silent-failure owns one named core action's completion rate, and looks for a swallowed exception behind a drop. Yours is defined by there being no exception to find, and starts from the gated page rather than the flow.

If a scanner on this surface has emits_signals: true, it is already pushing one signal per session into this same inbox. Never re-author what it pushed. Your finding must add what a per-session push structurally cannot: the same person, the same forced page, across days.

SQL footguns

  1. Client clocks lie. Sessions and events arrive dated into the future. Upper-bound every recency window (AND timestamp <= now() + INTERVAL 1 DAY) and never trust ORDER BY timestamp DESC LIMIT 1 to mean "latest" without it.
  2. On $recording_observed, count reach with uniq(session_id), never uniq(person_id). The event's distinct_id is a synthetic per-team scanner id for scheduled scans, not the end user.
  3. Group scanners by scanner_id, never scanner_name. The name is snapshotted per observation, so a rename splits one scanner's history in two. Carry the name as a label via argMax(properties.scanner_name, timestamp).
  4. scanner_output_tags is a JSON-encoded array, not a native one. JSONExtract(..., 'Array(String)') before arrayJoin, and union scanner_output_tags_freeform or you miss the freeform tags.
  5. Only succeeded observations write $recording_observed. Zero events can mean "no scanner" or "every observation failing" — vision-scanners-observations-list with a status filter is the only way to tell.

The discriminator

Emit a report when, for a gated page, returning people are redirected to it and leave without completing, at a rate that has stepped away from that page's trailing two-week norm for the same day of week.

Three things have to hold together:

  1. Repeat arrivals. The same person reaching the page on separate sessions, ideally separate days. One visit is someone who got distracted; four is a wall.
  2. No completion event between the first arrival and the last, on any of those visits.
  3. No matching exception, where Error Tracking is connected. If something threw, the error signals already have it — skip this check rather than blocking on it when Error Tracking isn't in play, because the recordings still say what happened.

Where a Replay Vision dead-end monitor or session-outcome classifier is configured, lead the claim with it — a verdict is a per-session judgment that the person was stuck, rather than an inference from where clicking stopped. Name the scanner so the reader can open the observations.

Write nothing when

  • The page is a natural terminus. Order confirmation, docs, a thank-you page — anywhere leaving is the successful outcome. Learn these from the trailing norm, not a hardcoded list.
  • Arrivals are first-time rather than returning. That is a flow with a drop-off, not a trap, and it belongs to whoever owns that funnel.
  • Traffic to the page changed enough to explain the rate on its own.
  • Exceptions rose alongside it, per the third condition above.

The bar

A page needs enough sessions for the rate to mean anything. Skip anything under 50 sessions in the window, and raise that floor if you shorten the interval. Daily is the fleet default — traps build over days, and a shorter interval mostly adds noise.

What to gather before writing

The report has to name what is blocking people, not just a page and a number.

  1. The rate and its norm — what it was, what it is, over how many sessions.
  2. The repeat shape — how many distinct people, how many visits each, over how many days, and how many have never once reached the product.
  3. Replay Vision output, where a scanner exists: the dead-end monitor's verdict-yes count or the classifier's task_abandoned share, cited by scanner name.
  4. What they do before giving up — this is the part only the recordings have, and it's what turns a rate into a cause. Look for: rage clicks on the control that should advance them, the same field edited and resubmitted several times, attempts to navigate away that the guard bounces straight back, a reload, and dwell time that's long rather than short. Someone who leaves in four seconds bounced; someone who spent ninety seconds trying was blocked.
  5. Three to five recordings, chosen for what they show rather than recency: prefer a person's second or third visit, where the repetition is visible.
  6. Deploys touching that route, if the codebase is connected.
  7. Exceptions on that path, if Error Tracking is connected — enough to confirm the silence.

Writing the report

Lead with the flow and who is stuck in it, not the page's exit rate. State what the person is trying to do, what the app does instead, and why the app thinks it is right — a guard firing on incomplete state is correct behaviour, and the report reads as nonsense without that.

Suggest a fix only when the evidence supports one. "Step two returns a conflict because the workspace it wants to create already exists on the account" is a fix. "Improve onboarding" is not, and is worse than saying you don't know.

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.