Skip to content

feat(replay-vision): fit scanner filters to volume and rework default scanners - #449

Open
ksvat wants to merge 7 commits into
mainfrom
posthog/replay-vision-grounded-scanner-filters
Open

ksvat wants to merge 7 commits into
mainfrom
posthog/replay-vision-grounded-scanner-filters

Conversation

@ksvat

@ksvat ksvat commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Merge after #448: the volume tiers read the recordings and scannable-session counts that #448 adds to the enable-replay handoff. Without it, the scanner tasks read the counts themselves.

Problem

Many wizard-created Replay Vision scanners are enabled but never write an observation. Most wizard projects record few sessions, yet the wizard always narrows its scanners with filters guessed from the repo: a $rageclick gate and a $current_url page filter. On a small project these filters often match nothing. The wizard also never creates a classifier, the scanner type people open most often when they build scanners in the app.

Changes

  • Volume tiers (replay-vision-scanners-core): the agent reads the project's sessions per month from one unfiltered estimate.
    • No scannable sessions yet (no recordings, or only recordings too short to scan): no filters, free_only: true, sized for 250 sessions a month.
    • 1 to 249 sessions a month: no filters, free_only: true. Sampling comes from free_only_monthly_credits_left (org-wide), adding back this setup's own earlier scanners, with a 0.01 floor.
    • 250 sessions a month or more: targeted queries, with visited_page paths grounded in real recordings and checked with vision-scanners-estimate before create.
  • The key flow outcome classifier has no page fallback: if its flow scope matches no session, it is skipped, never widened.
  • New default scanners for wizard replay-vision:
    • A problems monitor: breakage and getting stuck in one scanner.
    • A key flow outcome classifier: completed / abandoned / blocked-by-error / confused / not-attempted.
    • Session summaries.
  • Re-runs read the earlier tier from free_only, keep credit_limit, and keep the query and rate unless the tier changed. They never change free_only from false to true. High volume sets free_only: false.
  • A breakage monitor from an earlier run is updated in place into the problems monitor. An earlier frustration monitor is left alone and reported as overlapping.
  • Self-driving step 6c keeps its breakage and frustration monitors. Its frustration monitor falls back to active sessions, excluding the breakage pages, when the project has no rage clicks.

How did you test this code?

npm test (220 passed) and npm run build pass, and the build packages both new briefs. The query shapes (visited_page regex, not_regex, active_seconds, unfiltered estimate) and the page-path SQL return results against a real project through the MCP. A combined pages-OR-rage-click query did not widen the match, so the briefs do not use it. No full wizard run against a test app.


🤖 Generated with Claude Code

Every scanner task now estimates its exact query before creating it, and never creates a query that matches nothing in a project that records sessions. The breakage monitor scopes pages with a `visited_page` regex grounded in real recorded paths instead of a `$current_url` event filter. The frustration monitor falls back to active sessions at a low rate, excluding the breakage pages, when the project has no rage clicks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75
@ksvat ksvat self-assigned this Oct 30, 2026
@github-actions

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci ai-observability
  • /wizard-ci basic-integration
  • /wizard-ci feature-flags
  • /wizard-ci mcp-analytics
  • /wizard-ci replay-vision
  • /wizard-ci revenue
  • /wizard-ci self-driving
  • /wizard-ci warehouse
  • /wizard-ci warehouse-seeded

Test an individual app:

  • /wizard-ci ai-observability/anthropic
  • /wizard-ci ai-observability/google-adk
  • /wizard-ci ai-observability/groq
Show more apps
  • /wizard-ci ai-observability/manual-capture
  • /wizard-ci ai-observability/openai
  • /wizard-ci ai-observability/openai-agents
  • /wizard-ci ai-observability/opentelemetry
  • /wizard-ci ai-observability/vercel-ai
  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/flutter
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci feature-flags/django
  • /wizard-ci feature-flags/next-js
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci replay-vision/javascript-node
  • /wizard-ci replay-vision/next-js
  • /wizard-ci replay-vision/react-vite
  • /wizard-ci revenue/stripe
  • /wizard-ci self-driving/astro
  • /wizard-ci self-driving/fastapi
  • /wizard-ci self-driving/nuxt
  • /wizard-ci self-driving/react-router
  • /wizard-ci self-driving/sveltekit
  • /wizard-ci warehouse/monorepo-env
  • /wizard-ci warehouse/multi-source-next
  • /wizard-ci warehouse/stripe-node
  • /wizard-ci warehouse/zero-source
  • /wizard-ci warehouse-seeded/next-stripe
  • /wizard-ci warehouse-seeded/next-stripe-declined

Test against a wizard branch:

  • /wizard-ci all wizard:my-branch

Add wizard:<branch> to any command above to pin the wizard branch. It defaults to main.

Results will be posted here when complete.

@ksvat
ksvat marked this pull request as ready for review October 30, 2026 17:21
@ksvat
ksvat requested a review from a team as a code owner October 30, 2026 17:21
@ksvat
ksvat requested review from TueHaulund and fasyy612 and removed request for a team October 30, 2026 17:21
…tcome classifier

The replay-vision command now picks a tier from the project's monthly sessions. Low-volume projects get scanners with no filters, sampled to fit the free credits. Projects with no recordings yet get unfiltered scanners with free_only on. Busy projects keep the targeted, grounded queries.

The command now creates a problems monitor (breakage and getting stuck in one scanner), a key flow outcome classifier, and session summaries. The breakage and frustration briefs stay for self-driving step 6c. Page scope rules move to the shared core skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75
@ksvat ksvat changed the title fix(replay-vision): check scanner queries against real sessions feat(replay-vision): fit scanner filters to volume and rework default scanners Oct 30, 2026
@ksvat
ksvat requested a review from a team October 30, 2026 17:37
The free credits cover about 500 scans a month on the default model, so scanners with no filters only make sense for projects under about 250 sessions a month. Busier projects use the targeted queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75
1.0, and add `"free_only": true`: the scanner then never adds to the bill,
and pauses if the organization's free credits run out. Say in your handoff
that the volume was unknown.
- **Low volume** (under 250 sessions a month, about what the free credits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

The low tier has no cap: three unfiltered scanners, no credit_limit, no free_only. It also sizes from the org-wide free credits, ignoring what other scanners already spend. Set "free_only": true and size from free_only_monthly_credits_left?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 940d1f9.

- **No recordings yet, or low volume**: no filters — `{"kind":
"RecordingsQuery"}`. `not-attempted` then tells the user how many sessions
never reach the flow.
- **High volume**: a `visited_page` scope on the flow's first page, at

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

If this scope matches nothing, core says "follow your brief's fallback", but there isn't one here. The agent lands on core's busiest paths and classifies a flow that isn't there. Say "skip the classifier and explain why"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 940d1f9.

…an unmatched classifier

Low-volume scanners have no filters, so their spend grows with traffic. They are now free_only, and their sampling rate is sized from free_only_monthly_credits_left, so it accounts for credits the organization already used.

The flow outcome classifier has no page fallback: when its flow scope matches no session, it is skipped. The busiest-paths fallback moves from the core skill into the problems and breakage briefs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75
Comment on lines +80 to +82
that the organization's own scanners need. Set `sampling_rate` to
`min(1, share × free_left / (sessions_per_month ×
credits_per_observation))`, rounded down to two decimals. `share` is in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

Rounding down with no floor turns any rate below 0.01 into 0, which pauses the scanner. So 1 credit left is worse than 0 left, which gets 0.1. Floor at 0.01?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08fe761.

Comment on lines +83 to +89
your brief. `free_left` is `free_only_monthly_credits_left` and
`credits_per_observation` comes from the same estimate of this scanner's
query: the free credits still left after what the organization already
used and its other free-only scanners. The three scanners' shares add up
to 1, so together they fit what is left. When `free_left` is 0, create the
scanner at `sampling_rate` 0.1 and say in your handoff that it waits until
the free credits reset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

free_left already subtracts the scanners this setup made on a previous run, so rates shrink on every rerun. It's also this month's leftover, but the rate never changes after the reset. Size from the monthly ceiling minus the other free-only scanners instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08fe761.

Comment on lines +90 to +91
- **High volume** (250 sessions a month or more): your brief's targeted
query and sampling rate, checked against real sessions as below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

Nothing sets free_only: false here. A project that grows past 250 a month keeps free-only scanners, and they pause every month.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08fe761.

Comment on lines +68 to +69
= `matched_sessions_in_window` / `window_days` × 30. Reuse the enable-replay
handoff's count when it gives one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

This needs #448 to land first, and the handoff to include window_days. Also, L41 ("don't change sampling rates") contradicts the tiers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08fe761.

Comment on lines +114 to +123
```sql
SELECT path(url) AS page, uniq(session_id) AS sessions
FROM session_replay_events
ARRAY JOIN all_urls AS url
WHERE start_time >= now() - INTERVAL 7 DAY
GROUP BY page
HAVING sessions >= 5
ORDER BY sessions DESC
LIMIT 100
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

On small projects HAVING sessions >= 5 can return no rows, and nothing covers that case. Also path(url) drops the # part, so on hash-routed apps every page becomes /.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08fe761.

Comment on lines +44 to +51
**Earlier runs of this command** created a separate breakage monitor and a
frustration monitor (`emits_signals: false`). A breakage monitor from an
earlier run (match phrase `moments where the product visibly broke for the
user`) is this brief's predecessor: update it in place into this brief, per
the core re-run rules. Leave a frustration monitor from an earlier run
(match phrase `clear signs the user got stuck or frustrated`) untouched, and
say in your handoff that it now overlaps this monitor, so the user can turn
it off.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

When the old breakage monitor is updated in place, what happens to its credit_limit and to a rate the user set themselves?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08fe761.

…tings on re-runs

Low-volume sampling now comes from the monthly free credits minus other free-only scanners, leaving out this setup's own earlier scanners, with a 0.01 floor. High volume sets free_only off, and a re-run moves a scanner's query, rate and free_only only when its tier changed. credit_limit is never changed. Tier counts come from the enable-replay handoff or are read directly. The page query keeps hash routes and covers an empty result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75

ksvat commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

/wizard-ci replay-vision

@wizard-ci-bot

wizard-ci-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🧙 Wizard CI Results

Trigger ID: 3ce486e
Workflow: View run

App Confidence PR YARA
replay-vision/javascript-node/inventory-api N/A Failed (logs)
replay-vision/next-js/artisan-ceramics 3/5 #4810 (logs) ✓
replay-vision/react-vite/court-booking 5/5 #4811 (logs) ✓

Configuration

Setting Value
Wizard ref main
Context Mill ref posthog/replay-vision-grounded-scanner-filters
PostHog ref master

Search for trigger ID 3ce486e in wizard-workbench PRs.

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75

ksvat commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

/wizard-ci replay-vision

@wizard-ci-bot

wizard-ci-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🧙 Wizard CI Results

Trigger ID: 7d6f00e
Workflow: View run

App Confidence PR YARA
replay-vision/javascript-node/inventory-api N/A Failed (logs)
replay-vision/next-js/artisan-ceramics 4/5 #4812 (logs) ✓
replay-vision/react-vite/court-booking 5/5 #4813 (logs) ✓

Configuration

Setting Value
Wizard ref main
Context Mill ref posthog/replay-vision-grounded-scanner-filters
PostHog ref master

Search for trigger ID 7d6f00e in wizard-workbench PRs.

`min(1, share × free_pool / (sessions_per_month ×
credits_per_observation))`, rounded down to two decimals, and never below
0.01. `share` is in your brief, and `credits_per_observation` comes from
the estimate of this scanner's query. `free_pool` is the monthly free

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

Free-only budgets draw on the org's total spend, paid scanners included (quota.py:750), and vision-scanners-list only sees this project. Could this size from the estimate's free_only_monthly_credits_left, adding back this setup's own earlier scanners? That keeps re-runs stable too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1db72d1.

such projects stay small. Use your brief's low-volume query (no filters)
at `sampling_rate` 1.0, with `"free_only": true`. Say in your handoff that
the volume was unknown.
- **Low volume** (under 250 sessions a month, about what the free credits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

Recordings > 0 with 0 scannable sessions divides by zero here, and the "never create a query that matches nothing" rule then blocks all three. Treat it like the no-recordings tier?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1db72d1.

- **`credit_limit`**: never change it. Setup never sets one, so it is always
the user's.
- **Same tier as before**: keep its `sampling_rate` and `free_only`. Read the
earlier tier from the scanner itself: `free_only` with no filters is a low

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

The high-tier summarizer has no filters and free_only: false, so it matches neither rule. Read the tier from free_only alone?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1db72d1.

- **Same tier as before**: keep its `sampling_rate` and `free_only`. Read the
earlier tier from the scanner itself: `free_only` with no filters is a low
tier; any filter is the high tier.
- **The tier changed** (the project grew past 250 sessions a month, or

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

The handoff tells users to turn off Free credits only, and the next re-run turns it back on. Never flip free_only from false to true on an update, and mention the mismatch in the handoff instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1db72d1.


Then pick the tier:

- **No recordings yet** (recordings is 0): the volume is unknown, and most

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

At rate 1.0 with no shares, the three can drain the free pool in days once traffic lands, and nothing re-tunes them. Apply the 0.4/0.4/0.2 shares here too, or tell the user to re-run setup once recordings arrive?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1db72d1.

```

A hash-routed app shows its pages after `#` (`/#/booking`), so anchor those
regexes on the fragment: `^https?://[^/]+/#/booking(/|\?|$)`. If the query

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agent-drafted, reviewed by Tue.

nit: this misses hash apps under a path (/app/#/booking). Something like ^https?://[^/]+(/[^#]*)?#/booking(/|\?|$)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1db72d1.

@sarahxsanders sarahxsanders left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @ksvat!! checked Tue's latest comments. before we ship, can you fix:

  • the free-credit budget calculation
  • handle recordings with 0 scannable sessions
  • make reruns preserve the summarizer's settings and the user's free-only choice?

we also need a run that creates the scanners, both supported-app runs deferred them because of missing perms

the node failure is expected so that isn't blocking

… on re-runs

Free-only sampling now comes from free_only_monthly_credits_left, which covers the whole organization, adding back this setup's own earlier scanners. A project whose recordings are all too short to scan uses the no-scannable-sessions tier instead of dividing by zero, and that tier now applies the free-credit shares. Re-runs read the earlier tier from free_only alone and never change it from false to true. The hash-route regex covers apps under a path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 158f9145-6821-4694-9719-574ec121ec75

ksvat commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

/wizard-ci replay-vision

@wizard-ci-bot

wizard-ci-bot Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

🧙 Wizard CI Results

Trigger ID: 9376f63
Workflow: View run

App Confidence PR YARA
replay-vision/javascript-node/inventory-api N/A Failed (logs)
replay-vision/next-js/artisan-ceramics 5/5 #4828 (logs) ✓
replay-vision/react-vite/court-booking 5/5 #4829 (logs) ✓

Configuration

Setting Value
Wizard ref main
Context Mill ref posthog/replay-vision-grounded-scanner-filters
PostHog ref master

Search for trigger ID 9376f63 in wizard-workbench PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants