Skip to content

feat(sessions): ship the sessions v3 schema with more ad click ids - #104566

Merged
trunk-io[bot] merged 13 commits into
masterfrom
lricoy/sessions-v3-more-ad-ids
Oct 30, 2026
Merged

trunk-io[bot] merged 13 commits into
masterfrom
lricoy/sessions-v3-more-ad-ids

Conversation

@lricoy

@lricoy lricoy commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Problem

  • Web analytics and replay cannot read sessions v3 in production: the rebuilt raw_sessions_v3 has a new shape, and HogQL still reads the old columns (entry_ad_ids_map, uniqExactMerge).
  • Until HogQL matches the table, no team can switch to v3 to get its faster single-session lookups, such as the replay "view recording" path.
  • Sessions also miss entry attribution for several ad platforms, so web analytics cannot filter or break down traffic from them:
  • This PR started as the ad ID change and stayed open while the table was rebuilt, so it is now the sessions v3 PR itself: schema, HogQL, ingestion and migration.

Changes

  • Teams can switch to sessions v3 with sessionTableVersion: "v3" in their HogQL modifiers. The default stays v2, so nothing changes for anyone else.
  • HogQL reads the new shape:
    • entry_ad_ids_set holds flat key=value pairs and replaces entry_ad_ids_map.
    • Counts use uniq states instead of uniqExact.
    • Flags come from flag_key_values, with a text index tokenized on =. flag_keys and the always-empty flag_values are gone.
  • raw_sessions_v3 attributes 19 new entry params:
Param Platform
utm_id, utm_source_platform, utm_creative_format, utm_marketing_tactic GA4
gad_campaignid Google Ads campaign ID
srsltid Google Merchant Center
rdt_cid Reddit
irclickid Impact
syclid Shopify
_bhlid beehiiv
awc Awin
tblci Taboola
yclid Yandex
ndclid Nextdoor
dicbo Outbrain
cjevent CJ Affiliate
rtid Rokt
oppref OpenAI Ads (PostHog/posthog-js#5046, #103854)
bfcid Freebuff (#117217, PostHog/posthog-js#5300)
  • The MV and backfill fall back to the page URL when a param is not an event property, so sessions get these params even before the SDK captures them.
    • The fallback runs only on $pageview and $screen events.
    • The scanned URL is capped at 16,384 characters and each value at 2,048.
  • Snapchat's ScCid, SsCid and sccid map to one sccid key. An import-time guard keeps an unsafe identifier out of the generated SQL.
  • An empty gclid or fbclid property no longer counts as a click ID, which matches v2. Channel type read "gclid": "" as a paid click.
  • The MVs and backfill drop events more than 31 days from their session ID's timestamp. Junk session IDs (1970, year 6339) otherwise put a session in its own monthly partition.
  • Deliberately not tracked: recipient identifiers (mkt_tok, _hsenc, mc_eid), HubSpot's hsa_* family, Triple Whale template params, and generic clickid.

Note

The ClickHouse team applied the cloud side by hand: the sessions-cluster table, the October backfill, the ingestion pipeline, and the Distributed tables on the main and events clusters (thread). As of 2026-10-28 prod-us is done; dev and EU are still to do.

  • Migration 0364 does nothing in cloud for that reason.
    • Elsewhere (fresh installs, CI, the test database) it creates the two v3 MVs again with CREATE … IF NOT EXISTS. Migration 0178 dropped them, and this is the only migration that brings them back.
    • It never drops or recreates an existing object. An install that built v3 before the shape change keeps the old shape; the migration comment explains how to reset it.
  • HCL: flag_values is gone, and prod-us raw_sessions_v3 now points at the sessions cluster, as prod-eu already did.
  • entry_ad_ids_set gets no bloom filter index, because ClickHouse rejects one on an AggregateFunction(argMin, ...) state column.
  • Mechanical: regenerated HCL golden/ and sql/, snapshots, and the HogQL $entry_* field lists.

Warning

Two schema items are still declared but outside this PR's reach:

  • Prod-us still declares the legacy sharded_raw_sessions_v3, which no longer exists there. It comes from the shared roles/data/common layer, so removing it for one env belongs in posthog-cloud-infra's overrides/.
  • The prod tables still have the flag_values column until the ClickHouse team drops it. No migration drops it in cloud, because the ingestion Distributed table must lose it first.

How did you test this code?

Test rationale:

  • test_raw_sessions_v3_model.py:
    • The URL fallback cases fail if the extraction drops URL-only params.
    • The Snapchat cases catch alias regressions.
    • The precedence cases catch a property losing to the URL, and an empty property blocking the fallback.
    • The empty click ID case fails if "gclid": "" counts as a paid click again.
    • A test through the live MVs drops a 1970 session ID and one more than 31 days after its event, and keeps a client clock 3 days ahead and a late event 30 days in.
  • test_sessions_v3.py covers channel type for 10 support-ticket shapes, entry UTMs across events, and replay rows that start a session or exist alone.
  • Ran locally against the dev ClickHouse, on a test database rebuilt from scratch: the migrations, schema, sessions v3 model and HogQL sessions v3 suites, and hcl/check.sh.
  • Not run locally: the multinode migration smoke. CI runs it.
  • The rebuilt prod-us table was compared with v2 for October through read-only queries: session counts, bounce, duration, pageviews and every new ad ID. Every sampled difference matched v3's documented rules when checked against the raw events.

Release status

  • No feature flag controls this change

Docs update

  • The "Sessions table" section of frontend/src/scenes/web-analytics/contributing.md explains how v3 picks entry and exit values, the empty click ID rule, the session timestamp guard, and the accepted gap between $entry_utm_* and $channel_type.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Fable 5 (initial ad ID work) and Claude Opus 5.5 (schema realignment, bfcid, rollout fixes and this description).

Skills invoked: /querying-production-databases-via-metabase, /clickhouse-migrations, /writing-tests, /writing-code-comments, /reviewing-with-coderabbit, /writing-pr-descriptions.

  • The param set came from a production URL-frequency measurement plus the open issues above. Committed test fixtures are invented.
  • The table shape follows the rebuild agreed in this thread.
  • The migration first recreated the ingestion pipeline in cloud. Once the ClickHouse team had built everything by hand, it became a no-op in cloud that only restores the local MVs.
  • Review dispositions: CodeRabbit's empty-property finding and veria's URL-scan finding are fixed. Greptile's "keep old columns" rule does not apply, because v3 served no queries during the rebuild.

@lricoy lricoy self-assigned this Sep 22, 2026
@trunk-io

trunk-io Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

😎 Stack merged successfully - details.

@greptile-apps

greptile-apps Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

The PR is not yet safe to merge because long entry URLs can silently lose advertising attribution.

Reviews (3) · Last reviewed commit: "fix(sessions): unblock migration replay ..."

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 0da3df47-58c0-4e2b-be46-c6104c9841ba

📥 Commits

Reviewing files that changed from the base of the PR and between 1450e8b and 8e66b60.

📒 Files selected for processing (5)
  • posthog/clickhouse/hcl/golden/local-single/all.hcl
  • posthog/clickhouse/hcl/sql/local-single/all.sql
  • posthog/clickhouse/migrations/0336_sessions_v3_more_ad_ids.py
  • posthog/clickhouse/migrations/max_migration.txt
  • posthog/clickhouse/test/__snapshots__/test_schema.ambr

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Walkthrough

Session V3 now extracts more advertising and UTM identifiers from event properties or, when those properties are empty, from supported URL parameters. The storage schema represents ad IDs as key=value entries, removes the ad-ID map and separate feature-flag key column, and uses approximate uniq aggregation for pageview, autocapture, and screen counts. New event and replay materialized views write session aggregate states. HogQL projections and tests include the expanded attribution properties.

Priority: Normal

Merge Risk: Low · up to 8e66b

This change expands session ad-ID attribution and changes the raw-session schema. One issue remains: a campaign ID taken from the URL can keep a + where the value captured from event properties has a space. The same ID can then be grouped as two different values. Fixing this is a small follow-up, and the change is otherwise mergeable once the ClickHouse owners coordinate the rollout.

Security architecture risk: Moderate · up to 8e66b

The migration changes how session data is rebuilt across environments. Local session rows are intentionally discarded, while the cloud ingestion change depends on a separately managed table rebuild and backfill. No new attacker-accessible entrypoint was identified, but the rollout and recovery sequence warrants review.

Retained concerns

  • Medium · reliability · inferred: Cloud ingestion objects are recreated in this migration, but the compatible sessions-cluster rebuild and backfill are delegated out of band. The available evidence does not establish an ordering or recovery barrier between those steps.
  • Low · reliability · observed: The non-cloud migration intentionally drops locally accumulated v3 session rows and provides no in-migration restoration. This is a destructive state transition for local deployments, even though the intended go-forward policy is documented.
Security review details

Security Blast Radius

  • inferred — An attacker who can influence an ingested pageview or screen URL can influence configured ad-ID attribution values for that session. The resulting storage change is within the sessions pipeline; the available evidence does not establish broader read privileges or cross-tenant access.

Trust Boundaries and Controls

  • observed — URL-derived values are decoded and capped at 2,048 characters, and URL lookups are confined to pageview and screen events. Session entry URLs were already selected for storage, limiting what can be concluded about newly exposed data from the attribution projection alone.

Resilience and Maintainability Implications

  • inferred — Retaining the Kafka consumer group and using conditional drops support resumption and reruns, but do not establish coordination with the separately managed cloud rebuild or guarantee recovery from partial DDL completion.

Hardening Proposals

  • proposed — Before cloud ingestion is resumed, establish and verify an operational ordering barrier for the compatible sessions-cluster schema and backfill, with an explicit recovery path for an interrupted migration.
Pre-merge checks: Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description includes all required sections and explains the problem, schema changes, testing, release status, documentation, and agent context. However, it contains a material migration-number err… Update every migration reference to the actual migration number, 0336, and reconcile the conflicting 0335 reference. Confirm that the cloud and non-cloud migration behavior described matches migration 0336. Also reduce the main description …
Full details: Description check

Explanation

The description includes all required sections and explains the problem, schema changes, testing, release status, documentation, and agent context. However, it contains a material migration-number error: it refers to migration 0364, while the pull request adds migration 0336. The objectives also mention 0335, creating conflicting migration references.

Resolution

Update every migration reference to the actual migration number, 0336, and reconcile the conflicting 0335 reference. Confirm that the cloud and non-cloud migration behavior described matches migration 0336. Also reduce the main description toward the template's 150-word target where practical, while retaining required test and rollout details.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Duplication (Python) — 6 new duplicated blocks (worst 344 tokens)

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

First copy Second copy Lines Tokens
posthog/hogql/database/schema/test/test_sessions_v2.py:343 posthog/hogql/database/schema/test/test_sessions_v3.py:433 82 344
posthog/hogql/database/schema/test/test_sessions_v2.py:158 posthog/hogql/database/schema/test/test_sessions_v3.py:281 95 343
posthog/hogql/database/schema/test/test_sessions_v2.py:192 posthog/hogql/database/schema/test/test_sessions_v3.py:289 62 315
posthog/hogql/database/schema/test/test_sessions_v2.py:67 posthog/hogql/database/schema/test/test_sessions_v3.py:64 69 282
posthog/hogql/database/schema/test/test_sessions_v2.py:301 posthog/hogql/database/schema/test/test_sessions_v3.py:391 43 199
posthog/hogql/database/schema/test/test_sessions_v2.py:267 posthog/hogql/database/schema/test/test_sessions_v3.py:357 35 169
✅ Duplication (TypeScript) — clean

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

⚠️ Bundle size — 🔺 +379.1 KiB (+0.5%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 78.86 MiB · 🔺 +379.1 KiB (+0.5%)

File Size Δ vs base
render-query/src/render-query/render-query.js 25.04 MiB 🔺 +165.3 KiB (+0.6%)
exporter/_parent/products/signals/frontend/inbox/logics/reportGithubAccessLogic.js 130.3 KiB 🔺 +130.3 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityTraceScene.js 227.6 KiB 🔺 +31.9 KiB (+16.3%)
posthog-app/_parent/products/signals/frontend/inbox/InboxScene.js 483.5 KiB 🟢 -31.7 KiB (-6.1%)
posthog-app/src/scenes/web-analytics/WebAnalyticsScene.js 317.1 KiB 🔺 +30.7 KiB (+10.7%)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.91 MiB 🔺 +8.4 KiB (+0.2%)
posthog-app/_parent/products/workflows/frontend/WorkflowsScene.js 71.0 KiB 🔺 +8.0 KiB (+12.8%)
posthog-app/src/scenes/marketing-analytics/MarketingAnalyticsScene.js 198.0 KiB 🔺 +6.0 KiB (+3.1%)
exporter/src/queries/schema.js 1.27 MiB 🔺 +3.9 KiB (+0.3%)
posthog-app/src/queries/schema.js 1.27 MiB 🔺 +3.9 KiB (+0.3%)
posthog-app/_parent/products/data_modeling/frontend/ModelsScene.js 89.9 KiB 🟢 -3.8 KiB (-4.0%)
posthog-app/_parent/products/business_intelligence/frontend/BusinessIntelligenceScene.js 179.9 KiB 🔺 +3.7 KiB (+2.1%)
posthog-app/_parent/products/workflows/frontend/Transactional/TransactionalScene.js 36.4 KiB 🔺 +3.5 KiB (+10.6%)
posthog-app/_parent/products/business_intelligence/frontend/BIWorksheetsScene.js 18.0 KiB 🔺 +3.4 KiB (+23.5%)
posthog-app/src/scenes/hog-functions/HogFunctionScene.js 70.5 KiB 🔺 +3.3 KiB (+4.9%)
posthog-app/src/scenes/AuthenticatedShell.js 274.6 KiB 🔺 +2.3 KiB (+0.9%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene.js 92.3 KiB 🔺 +2.3 KiB (+2.5%)
exporter/_parent/products/workflows/frontend/Workflows/Workflow.js 229.2 KiB 🔺 +2.2 KiB (+1.0%)
posthog-app/_parent/products/signals/frontend/inbox/logics/reportGithubAccessLogic.js 2.2 KiB 🔺 +2.2 KiB (new)
posthog-app/_parent/products/workflows/frontend/Workflows/WorkflowScene.js 121.4 KiB 🔺 +1.7 KiB (+1.4%)
posthog-app/_parent/products/tracing/frontend/TracingScene.js 163.6 KiB 🔺 +1.6 KiB (+1.0%)
posthog-app/_parent/products/endpoints/frontend/EndpointScene.js 64.8 KiB 🔺 +1.4 KiB (+2.3%)
posthog-app/_parent/products/data_modeling/frontend/nodeDetail/NodeDetailScene.js 46.4 KiB 🔺 +1009 B (+2.2%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

⚠️ Eager graph — 1 over budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.03 MiB · 25 files 🔺 +3.5 KiB (+0.3%) █████████░ 90.3% of 1.14 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
3.00 MiB · 623 files 🔺 +9.7 KiB (+0.3%) ██████████ 99.0% of 3.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.49 MiB · 2,544 files 🔺 +7.6 KiB (+0.1%) ██████████ 100.0% of 7.50 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.90 MiB · 3,708 files 🔺 +25.9 KiB (+0.3%) ██████████ 98.9% of 10.01 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.67 MiB · 2,636 files 🔺 +8.0 KiB (+0.1%) ██████████ 99.1% of 7.74 MiB
🟡 AI-first home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
7.92 MiB · 2,630 files 🔺 +8.4 KiB (+0.1%) ██████████ 100.2% of 7.91 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.34 MiB · 3,484 files 🔺 +22.6 KiB (+0.2%) ██████████ 97.4% of 9.59 MiB
web analytics scene
src/scenes/web-analytics/WebAnalyticsScene.tsx
10.30 MiB · 3,751 files 🔺 +53.4 KiB (+0.5%) ██████████ 95.1% of 10.83 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
8.35 MiB · 3,073 files 🔺 +11.2 KiB (+0.1%) █████████░ 88.1% of 9.47 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/zod/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/billing/billingLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/ProductEmptyState/productSetupPreloadLogic.ts stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/dashboards/frontend/widgets/previews/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/wizard/frontend/runs/WizardRunDetailsDrawer.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayShell.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayContextPanelFloating.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodaySidePanel.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayTopBar.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/todayRailItems.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 zod/v4/locales/de.js stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 packages/quill/packages/charts/ stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/VisualizationWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/RecordingsWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/api/primitives.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/notebooks/Notebook/notebookLogic.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/LiveMetricsDashboard/LiveWebAnalyticsMetrics.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 products/product_analytics/frontend/insights/trends/WorldMap/countryVectors.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/scenes/settings/SettingsMap.tsx stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 @jspm/core/nodelibs/browser/fs stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.7 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
4.0 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.7 KiB src/index.tsx
1.7 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.7 KiB src/lib/utils/bootTiming.ts
1.2 KiB src/RootErrorBoundary.tsx
949 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
86.7 KiB src/lib/api.ts
67.9 KiB src/lib/lemon-ui/icons/icons.tsx
41.9 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
36.4 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
27.0 KiB ../node_modules/.pnpm/tailwind-merge@2.2.2/node_modules/tailwind-merge/dist/bundle-mjs.mjs
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
86.7 KiB src/lib/api.ts
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
86.7 KiB src/lib/api.ts
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
187.9 KiB src/queries/validators.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
316.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
102.6 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.33 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.33 MiB · 18 files 🔺 +6.0 KiB (+0.3%) ████░░░░░░ 40.8% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 40 files 🔺 +10 B (+0.0%) n/a — loads on demand
Loader dist/toolbar.js 1.2 KiB no change █░░░░░░░░░ 6.0% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
940.2 KiB dist/toolbar/toolbar-app-XCB2SKCB.css
697.2 KiB dist/toolbar/chunk-chunk-GLH7EOTZ.js
261.2 KiB dist/toolbar/chunk-chunk-IIW2OHDL.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
130.4 KiB dist/toolbar/chunk-chunk-WCFJXEB6.js
75.7 KiB dist/toolbar/toolbar-app-X37MDLC2.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-DXUHSQKN.js
21.7 KiB dist/toolbar/chunk-chunk-2SQRISWU.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🔺 +3.82 MiB (+0.4%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 983.42 MiB · 🔺 +3.82 MiB (+0.4%)

✅ Playwright — all passed

All tests passed.

View test results →

⚠️ Backend snapshots — 1 updated (1 modified, 0 added, 0 deleted)

Query snapshots: Backend query snapshots updated

Changes: 1 snapshots (1 modified, 0 added, 0 deleted)

What this means:

  • Query snapshots have been automatically updated to match current output
  • These changes reflect modifications to database queries or schema

Next steps:

  • Review the query changes to ensure they're intentional
  • If unexpected, investigate what caused the query to change

Review snapshot changes →

⚠️ Backend coverage — 96.0% of changed backend lines covered — 2 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████████░ 96.0% (51 / 53)

File Patch Uncovered changed lines
posthog/conftest.py 50.0% 517
posthog/raw_sessions_v3_ad_ids.py 85.7% 58

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 574542922937897 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
warehouse_sources_queue ██████░░░░░░░░░░░░░░ 29.1% 92 / 316
demo ████████████░░░░░░░░ 57.8% 1,545 / 2,673
data_tools ████████████░░░░░░░░ 61.2% 90 / 147
aeo ██████████████░░░░░░ 70.5% 467 / 662
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
batch_exports ████████████████░░░░ 81.0% 21,250 / 26,225
apm █████████████████░░░ 84.1% 1,306 / 1,553
ml_inference █████████████████░░░ 86.7% 450 / 519
cdp ██████████████████░░ 88.2% 4,527 / 5,133
signals ██████████████████░░ 88.7% 51,721 / 58,335
product_tours ██████████████████░░ 89.3% 1,331 / 1,491
dashboards ██████████████████░░ 89.8% 7,234 / 8,056
data_warehouse ██████████████████░░ 89.9% 13,738 / 15,285
cohorts ██████████████████░░ 90.2% 8,238 / 9,138
notebooks ██████████████████░░ 90.2% 15,287 / 16,945
mcp_analytics ██████████████████░░ 90.6% 4,696 / 5,181
streamlit_apps ██████████████████░░ 90.7% 2,625 / 2,895
managed_warehouse ██████████████████░░ 90.9% 10,215 / 11,234
tasks ██████████████████░░ 91.1% 73,305 / 80,461
engineering_analytics ██████████████████░░ 91.3% 9,999 / 10,946
data_modeling ██████████████████░░ 91.4% 10,490 / 11,479
business_knowledge ██████████████████░░ 91.6% 6,899 / 7,528
exports ██████████████████░░ 91.8% 9,685 / 10,555
ai_training ██████████████████░░ 92.2% 356 / 386
conversations ███████████████████░ 92.5% 28,705 / 31,025
early_access_features ███████████████████░ 92.6% 1,341 / 1,448
managed_migrations ███████████████████░ 92.7% 1,581 / 1,705
visual_review ███████████████████░ 92.8% 9,244 / 9,966
canvas ███████████████████░ 92.8% 6,877 / 7,409
error_tracking ███████████████████░ 93.0% 15,699 / 16,882
approvals ███████████████████░ 93.0% 3,919 / 4,214
mcp_registry ███████████████████░ 93.1% 1,670 / 1,794
notifications ███████████████████░ 93.2% 1,145 / 1,229
slack_app ███████████████████░ 93.2% 13,528 / 14,519
surveys ███████████████████░ 93.3% 6,571 / 7,040
stamphog ███████████████████░ 93.4% 7,716 / 8,262
context_layer ███████████████████░ 93.8% 3,373 / 3,595
web_analytics ███████████████████░ 93.9% 21,653 / 23,051
ai_observability ███████████████████░ 94.0% 20,565 / 21,869
alerts ███████████████████░ 94.1% 8,553 / 9,094
billing_alerts ███████████████████░ 94.1% 2,094 / 2,226
mcp_store ███████████████████░ 94.4% 8,940 / 9,472
wizard ███████████████████░ 94.4% 5,791 / 6,134
reminders ███████████████████░ 94.8% 760 / 802
workflows ███████████████████░ 94.8% 13,379 / 14,111
review_hog ███████████████████░ 94.9% 11,490 / 12,109
customer_analytics ███████████████████░ 95.1% 24,457 / 25,717
annotations ███████████████████░ 95.1% 817 / 859
endpoints ███████████████████░ 95.1% 9,211 / 9,681
marketing_analytics ███████████████████░ 95.2% 18,893 / 19,846
legal_documents ███████████████████░ 95.2% 2,311 / 2,427
tracing ███████████████████░ 95.3% 3,330 / 3,495
logs ███████████████████░ 95.4% 15,235 / 15,967
growth ███████████████████░ 95.4% 9,812 / 10,282
actions ███████████████████░ 95.5% 756 / 792
posthog_ai ███████████████████░ 95.5% 2,438 / 2,553
messaging ███████████████████░ 95.9% 3,766 / 3,927
skills ███████████████████░ 95.9% 6,649 / 6,932
replay_vision ███████████████████░ 95.9% 26,697 / 27,825
autoresearch ███████████████████░ 96.0% 7,716 / 8,037
product_analytics ███████████████████░ 96.2% 28,496 / 29,618
access_control ███████████████████░ 96.2% 6,845 / 7,113
revenue_analytics ███████████████████░ 96.4% 1,876 / 1,946
user_interviews ███████████████████░ 96.5% 2,859 / 2,963
feature_flags ███████████████████░ 96.5% 24,761 / 25,661
experiments ███████████████████░ 96.7% 33,943 / 35,108
warehouse_sources ███████████████████░ 97.2% 442,080 / 454,800
data_quality ████████████████████ 97.7% 7,592 / 7,774
links ████████████████████ 97.9% 234 / 239
security ████████████████████ 98.1% 1,258 / 1,283
metrics ████████████████████ 98.1% 4,085 / 4,166
analytics_platform ████████████████████ 98.3% 2,778 / 2,827
data_catalog ████████████████████ 98.3% 3,932 / 3,999
pulse ████████████████████ 98.5% 2,043 / 2,075
live_debugger ████████████████████ 99.2% 626 / 631
field_notes ████████████████████ 99.4% 172 / 173

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

ℹ️ ClickHouse migration SQL — 2 migration(s)

ClickHouse migration SQL per cloud environment

  • unset
    • all
      ALTER TABLE sharded_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS flag_key_values SimpleAggregateFunction(groupUniqArrayArray(10000), Array(String)) AFTER has_autocapture
      ALTER TABLE writable_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS flag_key_values SimpleAggregateFunction(groupUniqArrayArray(10000), Array(String)) AFTER has_autocapture
      ALTER TABLE raw_sessions_v3
      ADD COLUMN IF NOT EXISTS flag_key_values SimpleAggregateFunction(groupUniqArrayArray(10000), Array(String)) AFTER has_autocapture
      ALTER TABLE sharded_raw_sessions_v3
      ADD INDEX IF NOT EXISTS flag_key_values_bloom_filter flag_key_values TYPE bloom_filter() GRANULARITY 1
      ALTER TABLE sharded_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS hosts SimpleAggregateFunction(groupUniqArrayArray(100), Array(String)) AFTER event_names
      ALTER TABLE writable_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS hosts SimpleAggregateFunction(groupUniqArrayArray(100), Array(String)) AFTER event_names
      ALTER TABLE raw_sessions_v3
      ADD COLUMN IF NOT EXISTS hosts SimpleAggregateFunction(groupUniqArrayArray(100), Array(String)) AFTER event_names
      ALTER TABLE sharded_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS emails SimpleAggregateFunction(groupUniqArrayArray(10), Array(String)) AFTER hosts
      ALTER TABLE writable_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS emails SimpleAggregateFunction(groupUniqArrayArray(10), Array(String)) AFTER hosts
      ALTER TABLE raw_sessions_v3
      ADD COLUMN IF NOT EXISTS emails SimpleAggregateFunction(groupUniqArrayArray(10), Array(String)) AFTER hosts
      ALTER TABLE sharded_raw_sessions_v3
      ADD INDEX IF NOT EXISTS hosts_bloom_filter hosts TYPE bloom_filter() GRANULARITY 1
      ALTER TABLE sharded_raw_sessions_v3
      ADD INDEX IF NOT EXISTS emails_bloom_filter emails TYPE bloom_filter() GRANULARITY 1
      ALTER TABLE sharded_raw_sessions_v3
      DROP INDEX IF EXISTS flag_keys_bloom_filter
      ALTER TABLE sharded_raw_sessions_v3
      DROP INDEX IF EXISTS flag_key_values_bloom_filter
      ALTER TABLE sharded_raw_sessions_v3
      DROP COLUMN IF EXISTS flag_keys
      ALTER TABLE writable_raw_sessions_v3
      DROP COLUMN IF EXISTS flag_keys
      ALTER TABLE raw_sessions_v3
      DROP COLUMN IF EXISTS flag_keys
      CREATE MATERIALIZED VIEW IF NOT EXISTS raw_sessions_v3_mv
      TO posthog_test.writable_raw_sessions_v3
      AS
      
      WITH
        
              JSONExtract(properties, 'Tuple(
                  `$current_url` Nullable(String),
                  `$external_click_url` Nullable(String),
                  `$browser` Nullable(String),
                  `$browser_version` Nullable(String),
                  `$os` Nullable(String),
                  `$os_version` Nullable(String),
                  `$device_type` Nullable(String),
                  `$viewport_width` Nullable(Int64),
                  `$viewport_height` Nullable(Int64),
                  `$geoip_country_code` Nullable(String),
                  `$geoip_subdivision_1_code` Nullable(String),
                  `$geoip_subdivision_1_name` Nullable(String),
                  `$geoip_city_name` Nullable(String),
                  `$geoip_time_zone` Nullable(String),
                  `$referring_domain` Nullable(String),
                  `utm_source` Nullable(String),
                  `utm_campaign` Nullable(String),
                  `utm_medium` Nullable(String),
                  `utm_term` Nullable(String),
                  `utm_content` Nullable(String),
                  `gclid` Nullable(String),
                  `gad_source` Nullable(String),
                  `fbclid` Nullable(String),
                  `$host` Nullable(String),
                  `gclsrc` Nullable(String),
                  `dclid` Nullable(String),
                  `gbraid` Nullable(String),
                  `wbraid` Nullable(String),
                  `gad_campaignid` Nullable(String),
                  `srsltid` Nullable(String),
                  `msclkid` Nullable(String),
                  `twclid` Nullable(String),
                  `li_fat_id` Nullable(String),
                  `mc_cid` Nullable(String),
                  `igshid` Nullable(String),
                  `ttclid` Nullable(String),
                  `rdt_cid` Nullable(String),
                  `epik` Nullable(String),
                  `qclid` Nullable(String),
                  `sccid` Nullable(String),
                  `_kx` Nullable(String),
                  `irclid` Nullable(String),
                  `irclickid` Nullable(String),
                  `utm_id` Nullable(String),
                  `utm_source_platform` Nullable(String),
                  `utm_creative_format` Nullable(String),
                  `utm_marketing_tactic` Nullable(String),
                  `yclid` Nullable(String),
                  `tblci` Nullable(String),
                  `dicbo` Nullable(String),
                  `ndclid` Nullable(String),
                  `cjevent` Nullable(String),
                  `awc` Nullable(String),
                  `syclid` Nullable(String),
                  `_bhlid` Nullable(String),
                  `rtid` Nullable(String),
                  `oppref` Nullable(String),
                  `bfcid` Nullable(String)
              )') as p,
              JSONExtractString(person_properties, 'email') as _person_email,
              tupleElement(p, '$current_url') as _current_url,
              if(event = '$pageview' OR event = '$screen', left(coalesce(_current_url, ''), 16384), '') as _attribution_url,
              tupleElement(p, '$external_click_url') as _external_click_url,
              tupleElement(p, '$browser') as _browser,
              tupleElement(p, '$browser_version') as _browser_version,
              tupleElement(p, '$os') as _os,
              tupleElement(p, '$os_version') as _os_version,
              tupleElement(p, '$device_type') as _device_type,
              tupleElement(p, '$viewport_width') as _viewport_width,
              tupleElement(p, '$viewport_height') as _viewport_height,
              tupleElement(p, '$geoip_country_code') as _geoip_country_code,
              tupleElement(p, '$geoip_subdivision_1_code') as _geoip_subdivision_1_code,
              tupleElement(p, '$geoip_subdivision_1_name') as _geoip_subdivision_1_name,
              tupleElement(p, '$geoip_city_name') as _geoip_subdivision_city_name,
              tupleElement(p, '$geoip_time_zone') as _geoip_time_zone,
              tupleElement(p, '$referring_domain') as _referring_domain,
              tupleElement(p, 'utm_source') as _utm_source,
              tupleElement(p, 'utm_campaign') as _utm_campaign,
              tupleElement(p, 'utm_medium') as _utm_medium,
              tupleElement(p, 'utm_term') as _utm_term,
              tupleElement(p, 'utm_content') as _utm_content,
              tupleElement(p, 'gclid') as _gclid,
              tupleElement(p, 'gad_source') as _gad_source,
              tupleElement(p, 'fbclid') as _fbclid,
              coalesce(nullIf(tupleElement(p, 'gclsrc'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'gclsrc')), 2048)), '')) as gclsrc,
              coalesce(nullIf(tupleElement(p, 'dclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'dclid')), 2048)), '')) as dclid,
              coalesce(nullIf(tupleElement(p, 'gbraid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'gbraid')), 2048)), '')) as gbraid,
              coalesce(nullIf(tupleElement(p, 'wbraid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'wbraid')), 2048)), '')) as wbraid,
              coalesce(nullIf(tupleElement(p, 'gad_campaignid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'gad_campaignid')), 2048)), '')) as gad_campaignid,
              coalesce(nullIf(tupleElement(p, 'srsltid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'srsltid')), 2048)), '')) as srsltid,
              coalesce(nullIf(tupleElement(p, 'msclkid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'msclkid')), 2048)), '')) as msclkid,
              coalesce(nullIf(tupleElement(p, 'twclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'twclid')), 2048)), '')) as twclid,
              coalesce(nullIf(tupleElement(p, 'li_fat_id'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'li_fat_id')), 2048)), '')) as li_fat_id,
              coalesce(nullIf(tupleElement(p, 'mc_cid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'mc_cid')), 2048)), '')) as mc_cid,
              coalesce(nullIf(tupleElement(p, 'igshid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'igshid')), 2048)), '')) as igshid,
              coalesce(nullIf(tupleElement(p, 'ttclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'ttclid')), 2048)), '')) as ttclid,
              coalesce(nullIf(tupleElement(p, 'rdt_cid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'rdt_cid')), 2048)), '')) as rdt_cid,
              coalesce(nullIf(tupleElement(p, 'epik'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'epik')), 2048)), '')) as epik,
              coalesce(nullIf(tupleElement(p, 'qclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'qclid')), 2048)), '')) as qclid,
              coalesce(nullIf(tupleElement(p, 'sccid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'sccid')), 2048)), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'ScCid')), 2048)), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'SsCid')), 2048)), '')) as sccid,
              coalesce(nullIf(tupleElement(p, '_kx'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, '_kx')), 2048)), '')) as _kx,
              coalesce(nullIf(tupleElement(p, 'irclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'irclid')), 2048)), '')) as irclid,
              coalesce(nullIf(tupleElement(p, 'irclickid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'irclickid')), 2048)), '')) as irclickid,
              coalesce(nullIf(tupleElement(p, 'utm_id'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'utm_id')), 2048)), '')) as utm_id,
              coalesce(nullIf(tupleElement(p, 'utm_source_platform'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'utm_source_platform')), 2048)), '')) as utm_source_platform,
              coalesce(nullIf(tupleElement(p, 'utm_creative_format'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'utm_creative_format')), 2048)), '')) as utm_creative_format,
              coalesce(nullIf(tupleElement(p, 'utm_marketing_tactic'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'utm_marketing_tactic')), 2048)), '')) as utm_marketing_tactic,
              coalesce(nullIf(tupleElement(p, 'yclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'yclid')), 2048)), '')) as yclid,
              coalesce(nullIf(tupleElement(p, 'tblci'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'tblci')), 2048)), '')) as tblci,
              coalesce(nullIf(tupleElement(p, 'dicbo'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'dicbo')), 2048)), '')) as dicbo,
              coalesce(nullIf(tupleElement(p, 'ndclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'ndclid')), 2048)), '')) as ndclid,
              coalesce(nullIf(tupleElement(p, 'cjevent'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'cjevent')), 2048)), '')) as cjevent,
              coalesce(nullIf(tupleElement(p, 'awc'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'awc')), 2048)), '')) as awc,
              coalesce(nullIf(tupleElement(p, 'syclid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'syclid')), 2048)), '')) as syclid,
              coalesce(nullIf(tupleElement(p, '_bhlid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, '_bhlid')), 2048)), '')) as _bhlid,
              coalesce(nullIf(tupleElement(p, 'rtid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'rtid')), 2048)), '')) as rtid,
              coalesce(nullIf(tupleElement(p, 'oppref'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'oppref')), 2048)), '')) as oppref,
              coalesce(nullIf(tupleElement(p, 'bfcid'), ''), nullIf(toValidUTF8(left(decodeURLFormComponent(extractURLParameter(_attribution_url, 'bfcid')), 2048)), '')) as bfcid,
              CAST(arrayFilter(x -> x IS NOT NULL, [
                  if(gclsrc IS NOT NULL, concat('gclsrc=', gclsrc), NULL),
                  if(dclid IS NOT NULL, concat('dclid=', dclid), NULL),
                  if(gbraid IS NOT NULL, concat('gbraid=', gbraid), NULL),
                  if(wbraid IS NOT NULL, concat('wbraid=', wbraid), NULL),
                  if(gad_campaignid IS NOT NULL, concat('gad_campaignid=', gad_campaignid), NULL),
                  if(srsltid IS NOT NULL, concat('srsltid=', srsltid), NULL),
                  if(msclkid IS NOT NULL, concat('msclkid=', msclkid), NULL),
                  if(twclid IS NOT NULL, concat('twclid=', twclid), NULL),
                  if(li_fat_id IS NOT NULL, concat('li_fat_id=', li_fat_id), NULL),
                  if(mc_cid IS NOT NULL, concat('mc_cid=', mc_cid), NULL),
                  if(igshid IS NOT NULL, concat('igshid=', igshid), NULL),
                  if(ttclid IS NOT NULL, concat('ttclid=', ttclid), NULL),
                  if(rdt_cid IS NOT NULL, concat('rdt_cid=', rdt_cid), NULL),
                  if(epik IS NOT NULL, concat('epik=', epik), NULL),
                  if(qclid IS NOT NULL, concat('qclid=', qclid), NULL),
                  if(sccid IS NOT NULL, concat('sccid=', sccid), NULL),
                  if(_kx IS NOT NULL, concat('_kx=', _kx), NULL),
                  if(irclid IS NOT NULL, concat('irclid=', irclid), NULL),
                  if(irclickid IS NOT NULL, concat('irclickid=', irclickid), NULL),
                  if(utm_id IS NOT NULL, concat('utm_id=', utm_id), NULL),
                  if(utm_source_platform IS NOT NULL, concat('utm_source_platform=', utm_source_platform), NULL),
                  if(utm_creative_format IS NOT NULL, concat('utm_creative_format=', utm_creative_format), NULL),
                  if(utm_marketing_tactic IS NOT NULL, concat('utm_marketing_tactic=', utm_marketing_tactic), NULL),
                  if(yclid IS NOT NULL, concat('yclid=', yclid), NULL),
                  if(tblci IS NOT NULL, concat('tblci=', tblci), NULL),
                  if(dicbo IS NOT NULL, concat('dicbo=', dicbo), NULL),
                  if(ndclid IS NOT NULL, concat('ndclid=', ndclid), NULL),
                  if(cjevent IS NOT NULL, concat('cjevent=', cjevent), NULL),
                  if(awc IS NOT NULL, concat('awc=', awc), NULL),
                  if(syclid IS NOT NULL, concat('syclid=', syclid), NULL),
                  if(_bhlid IS NOT NULL, concat('_bhlid=', _bhlid), NULL),
                  if(rtid IS NOT NULL, concat('rtid=', rtid), NULL),
                  if(oppref IS NOT NULL, concat('oppref=', oppref), NULL),
                  if(bfcid IS NOT NULL, concat('bfcid=', bfcid), NULL)
              ]) AS Array(String)) as ad_ids_set,
              tupleElement(p, '$host') as _host,
          -- attribution properties from non-pageview/screen events should be deprioritized, so make the timestamp +/- 1 year so they sort last
          if (event = '$pageview' OR event = '$screen', timestamp, timestamp + toIntervalYear(1)) as pageview_prio_timestamp_min,
          if (event = '$pageview' OR event = '$screen', timestamp, timestamp - toIntervalYear(1)) as pageview_prio_timestamp_max
      SELECT
          team_id,
          `$session_id_uuid` AS session_id_v7,
        
      
          initializeAggregation('argMaxState', source_table.distinct_id, timestamp) as distinct_id,
          initializeAggregation('groupUniqArrayState', source_table.distinct_id) as distinct_ids,
      
          timestamp AS min_timestamp,
          timestamp AS max_timestamp,
          inserted_at AS max_inserted_at,
      
          -- urls - only update if the event is a pageview or screen
          if(_current_url IS NOT NULL AND (event = '$pageview' OR event = '$screen'), [_current_url], []) AS urls,
          initializeAggregation('argMinState', _current_url, pageview_prio_timestamp_min) as entry_url,
          initializeAggregation('argMaxState', _current_url, pageview_prio_timestamp_max) as end_url,
          initializeAggregation('argMaxState', _external_click_url, timestamp) as last_external_click_url,
      
          -- device
          initializeAggregation('argMinState', _browser, timestamp) as browser,
          initializeAggregation('argMinState', _browser_version, timestamp) as browser_version,
          initializeAggregation('argMinState', _os, timestamp) as os,
          initializeAggregation('argMinState', _os_version, timestamp) as os_version,
          initializeAggregation('argMinState', _device_type, timestamp) as device_type,
          initializeAggregation('argMinState', _viewport_width, timestamp) as viewport_width,
          initializeAggregation('argMinState', _viewport_height, timestamp) as viewport_height,
      
          -- geo ip
          initializeAggregation('argMinState', _geoip_country_code, timestamp) as geoip_country_code,
          initializeAggregation('argMinState', _geoip_subdivision_1_code, timestamp) as geoip_subdivision_1_code,
          initializeAggregation('argMinState', _geoip_subdivision_1_name, timestamp) as geoip_subdivision_1_name,
          initializeAggregation('argMinState', _geoip_subdivision_city_name, timestamp) as geoip_subdivision_city_name,
          initializeAggregation('argMinState', _geoip_time_zone, timestamp) as geoip_time_zone,
      
          -- attribution
          initializeAggregation('argMinState', _referring_domain, pageview_prio_timestamp_min) as entry_referring_domain,
          initializeAggregation('argMinState', _utm_source, pageview_prio_timestamp_min) as entry_utm_source,
          initializeAggregation('argMinState', _utm_campaign, pageview_prio_timestamp_min) as entry_utm_campaign,
          initializeAggregation('argMinState', _utm_medium, pageview_prio_timestamp_min) as entry_utm_medium,
          initializeAggregation('argMinState', _utm_term, pageview_prio_timestamp_min) as entry_utm_term,
          initializeAggregation('argMinState', _utm_content, pageview_prio_timestamp_min) as entry_utm_content,
          initializeAggregation('argMinState', _gclid, pageview_prio_timestamp_min) as entry_gclid,
          initializeAggregation('argMinState', _gad_source, pageview_prio_timestamp_min) as entry_gad_source,
          initializeAggregation('argMinState', _fbclid, pageview_prio_timestamp_min) as entry_fbclid,
      
          -- has gclid/fbclid for reading fewer bytes when calculating channel type
          -- clients send "gclid": "" and that is not a click id, so an empty value must not count as present
          initializeAggregation('argMinState', nullIf(_gclid, '') IS NOT NULL, pageview_prio_timestamp_min) as entry_has_gclid,
          initializeAggregation('argMinState', nullIf(_fbclid, '') IS NOT NULL, pageview_prio_timestamp_min) as entry_has_fbclid,
      
          -- other ad ids
          initializeAggregation('argMinState', ad_ids_set, pageview_prio_timestamp_min) as entry_ad_ids_set,
      
          -- channel type
          initializeAggregation('argMinState', tuple(_utm_source, _utm_medium, _utm_campaign, _referring_domain, nullIf(_gclid, '') IS NOT NULL, nullIf(_fbclid, '') IS NOT NULL, _gad_source), pageview_prio_timestamp_min) as entry_channel_type_properties,
      
      
          -- counts
          initializeAggregation('uniqState', if(event='$pageview', uuid, NULL)) as pageview_uniq,
          initializeAggregation('uniqState', if(event='$autocapture', uuid, NULL)) as autocapture_uniq,
          initializeAggregation('uniqState', if(event='$screen', uuid, NULL)) as screen_uniq,
      
          -- perf
          initializeAggregation('uniqUpToState(1)', if(event='$pageview' OR event='$screen', uuid, NULL)) as page_screen_uniq_up_to,
          event = '$autocapture' as has_autocapture,
      
          -- flags
          arrayMap((k, v) -> concat(k, '=', v), mapKeys(properties_group_feature_flags), mapValues(properties_group_feature_flags)) as flag_key_values,
      
          -- event names
          [event] as event_names,
      
          -- hosts
          if(_host IS NOT NULL AND _host != '', [_host], []) AS hosts,
      
          -- emails
          if(_person_email IS NOT NULL AND _person_email != '', [_person_email], []) AS emails,
      
          false as has_replay_events
      FROM posthog_test.sharded_events AS source_table
      WHERE bitAnd(bitShiftRight(toUInt128(accurateCastOrNull(`$session_id`, 'UUID')), 76), 0xF) == 7 -- has a session id and is valid uuidv7
      -- Clients can send a UUIDv7 session id with a garbage timestamp (1970, year 6339). Each one puts its session in its
      -- own monthly partition, and its events stretch the session duration across months. Keep only events within 31 days
      -- of the session id's timestamp. The bound is symmetric because the client mints the session id with its own clock
      -- while the server corrects event timestamps, so a client clock that runs days ahead is still a real session.
      AND fromUnixTimestamp64Milli(toUInt64(bitShiftRight(`$session_id_uuid`, 80))) BETWEEN timestamp - INTERVAL 31 DAY AND timestamp + INTERVAL 31 DAY
      AND TRUE
      CREATE MATERIALIZED VIEW IF NOT EXISTS raw_sessions_v3_recordings_mv
      TO posthog_test.writable_raw_sessions_v3
      AS
      
      WITH
          min_first_timestamp as timestamp,
          CAST(fromUnixTimestamp64Milli(9223372036854775), 'DateTime64(6)') as max_ts_64, -- max positive Int64 / 1000
          CAST(fromUnixTimestamp64Milli(-9223372036854775), 'DateTime64(6)') as min_ts_64, -- max negative Int64 / 1000
          CAST(NULL, 'Nullable(String)') as null_s,
          CAST(NULL, 'Nullable(Int64)') as null_i64,
          CAST(NULL, 'Nullable(UUID)') as null_uuid
      SELECT
          team_id,
          toUInt128(accurateCast(session_id, 'UUID')) AS session_id_v7,
          fromUnixTimestamp64Milli(toUInt64(bitShiftRight(session_id_v7, 80))) AS session_timestamp,
          initializeAggregation('argMaxState', source_table.distinct_id, min_ts_64) as distinct_id,
          initializeAggregation('groupUniqArrayState', source_table.distinct_id) as distinct_ids,
      
          timestamp AS min_timestamp,
          timestamp AS max_timestamp,
          fromUnixTimestamp(0) AS max_inserted_at,
      
          -- urls - only update if the event is a pageview or screen
          CAST([], 'Array(String)') AS urls,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_url,
          initializeAggregation('argMaxState', null_s, min_ts_64) as end_url,
          initializeAggregation('argMaxState', null_s, min_ts_64) as last_external_click_url,
      
          -- device
          initializeAggregation('argMinState', null_s, max_ts_64) as browser,
          initializeAggregation('argMinState', null_s, max_ts_64) as browser_version,
          initializeAggregation('argMinState', null_s, max_ts_64) as os,
          initializeAggregation('argMinState', null_s, max_ts_64) as os_version,
          initializeAggregation('argMinState', null_s, max_ts_64) as device_type,
          initializeAggregation('argMinState', null_i64, max_ts_64) as viewport_width,
          initializeAggregation('argMinState', null_i64, max_ts_64) as viewport_height,
      
          -- geo ip
          initializeAggregation('argMinState', null_s, max_ts_64) as geoip_country_code,
          initializeAggregation('argMinState', null_s, max_ts_64) as geoip_subdivision_1_code,
          initializeAggregation('argMinState', null_s, max_ts_64) as geoip_subdivision_1_name,
          initializeAggregation('argMinState', null_s, max_ts_64) as geoip_subdivision_city_name,
          initializeAggregation('argMinState', null_s, max_ts_64) as geoip_time_zone,
      
          -- attribution
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_referring_domain,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_utm_source,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_utm_campaign,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_utm_medium,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_utm_term,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_utm_content,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_gclid,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_gad_source,
          initializeAggregation('argMinState', null_s, max_ts_64) as entry_fbclid,
      
          -- has gclid/fbclid for reading fewer bytes when calculating channel type
          initializeAggregation('argMinState', false, max_ts_64) as entry_has_gclid,
          initializeAggregation('argMinState', false, max_ts_64) as entry_has_fbclid,
      
          -- other ad ids
          initializeAggregation('argMinState', CAST([], 'Array(String)'), max_ts_64) as entry_ad_ids_set,
      
          -- channel type
          initializeAggregation('argMinState', tuple(null_s, null_s, null_s, null_s, false, false, null_s), max_ts_64) as entry_channel_type_properties,
      
          -- counts
          initializeAggregation('uniqState', null_uuid) as pageview_uniq,
          initializeAggregation('uniqState', null_uuid) as autocapture_uniq,
          initializeAggregation('uniqState', null_uuid) as screen_uniq,
      
          -- perf
          initializeAggregation('uniqUpToState(1)', null_uuid) as page_screen_uniq_up_to,
          false as has_autocapture,
      
          -- flags
          CAST([], 'Array(String)') as flag_key_values,
      
          -- event names
          CAST([], 'Array(String)') as event_names,
      
          -- hosts
          CAST([], 'Array(String)') as hosts,
      
          -- emails
          CAST([], 'Array(String)') as emails,
      
          -- replay
          true as has_replay_events
      FROM posthog_test.sharded_session_replay_events AS source_table
      WHERE bitAnd(bitShiftRight(toUInt128(accurateCastOrNull(session_id, 'UUID')), 76), 0xF) == 7 -- has a session id and is valid uuidv7
      -- same session timestamp guard as the events SELECT. accurateCastOrNull because accurateCast throws on a malformed
      -- session_id, and ClickHouse can evaluate this condition before the uuidv7 check above filters those rows out
      AND fromUnixTimestamp64Milli(toUInt64(bitShiftRight(toUInt128(accurateCastOrNull(session_id, 'UUID')), 80))) BETWEEN min_first_timestamp - INTERVAL 31 DAY AND min_first_timestamp + INTERVAL 31 DAY
      AND TRUE
  • US, EU, DEV
    • 0364_sessions_v3_more_ad_ids — operations list is empty under this environment
    • data
      ALTER TABLE sharded_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS flag_key_values SimpleAggregateFunction(groupUniqArrayArray(10000), Array(String)) AFTER has_autocapture
      ALTER TABLE writable_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS flag_key_values SimpleAggregateFunction(groupUniqArrayArray(10000), Array(String)) AFTER has_autocapture
      ALTER TABLE raw_sessions_v3
      ADD COLUMN IF NOT EXISTS flag_key_values SimpleAggregateFunction(groupUniqArrayArray(10000), Array(String)) AFTER has_autocapture
      ALTER TABLE sharded_raw_sessions_v3
      ADD INDEX IF NOT EXISTS flag_key_values_bloom_filter flag_key_values TYPE bloom_filter() GRANULARITY 1
      ALTER TABLE sharded_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS hosts SimpleAggregateFunction(groupUniqArrayArray(100), Array(String)) AFTER event_names
      ALTER TABLE writable_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS hosts SimpleAggregateFunction(groupUniqArrayArray(100), Array(String)) AFTER event_names
      ALTER TABLE raw_sessions_v3
      ADD COLUMN IF NOT EXISTS hosts SimpleAggregateFunction(groupUniqArrayArray(100), Array(String)) AFTER event_names
      ALTER TABLE sharded_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS emails SimpleAggregateFunction(groupUniqArrayArray(10), Array(String)) AFTER hosts
      ALTER TABLE writable_raw_sessions_v3
      ADD COLUMN IF NOT EXISTS emails SimpleAggregateFunction(groupUniqArrayArray(10), Array(String)) AFTER hosts
      ALTER TABLE raw_sessions_v3
      ADD COL
      

…truncated. See the full SQL in the workflow logs.

✅ Hogbox preview — ready, open the preview

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend on the PostHog :master base (frontend unchanged by this PR)
🔗 Link stable across rebuilds: a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit 57e49e1 · box box-33be5b008eac · ready in 1282s (push → usable) · build log · rebuilds on every push, torn down on close

@trunk-io

trunk-io Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
Scenes-App/Notebooks/Nodes/Support Tickets WithTickets smoke-test Timed out waiting for the .LemonTable element to become visible. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

@lricoy
lricoy marked this pull request as ready for review September 22, 2026 16:39
@lricoy
lricoy requested a review from a team as a code owner September 22, 2026 16:39
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested review from a team September 22, 2026 16:39
@greptile-apps

greptile-apps Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.

  • P2 Migration includes application changes posthog/hogql/database/schema/sessions_v3.py:32 ▶

    This PR combines ClickHouse migration 0330 with application-facing HogQL schema and ad-ID behavior changes, including the new ad-ID catalog in posthog/raw_sessions_v3_ad_ids.py. Repository policy requires ClickHouse migration PRs to contain only the migration, its SQL definitions, and directly related tests. This requirement must be satisfied before merging: merge the schema migration first, then ship the HogQL and application changes separately.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@veria-ai

veria-ai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 2 · PR risk: 0/10

@lricoy lricoy closed this Sep 22, 2026
@lricoy lricoy reopened this Sep 22, 2026
@lricoy
lricoy requested review from andyzzhao and rorylshanks and removed request for a team September 22, 2026 20:36
@lricoy
lricoy force-pushed the lricoy/sessions-v3-more-ad-ids branch from dba2e5c to 7614d6d Compare September 25, 2026 01:28

lricoy commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

CI status: rebased and green except one pre-existing master failure

I rebased this branch onto master (it was 516 commits behind and conflicting), renumbered the ClickHouse migration 0331 → 0333 (master took 0331_metrics4_samples_timestamp_minmax and 0332_log_entries_aux), and addressed both review threads. The branch is now mergeable and every check passes except one shard, which fails for a reason that has nothing to do with this PR.

Failing check: Backend CI on Depot / Product tests (signals (2/2), field-notes, events schema legacy)

Why it is not this PR's:

  1. The same shard fails on master itself. On master tip 66deebf2 — the commit this branch is rebased onto — that check is already failure, in a run this PR has no part in (signals (1/2) passes, signals (2/2) fails).
  2. The failing tests are in files this PR does not touch. Per the test report for this head, 35 tests fail with "team with ID 3 was not found in the database", all in products/signals/backend/test/test_signal_queries.py and products/signals/backend/test/test_scout_suggestions.py. This PR changes 37 files, none under products/signals/.
  3. Byte-identical reproduction on plain master. I ran the full products/signals suite locally on this branch and on detached origin/master. Both give exactly 5 failed, 4946 passed, 34 errors, and the two sets of failing test IDs are identical (39 IDs, zero diff).

What the underlying bug looks like: the failures come from the autouse _scout_team_scope fixture in products/signals/backend/test/conftest.py, which raises TeamScopeError: Team <id> not found on default DB during setup. Both files pass standalone (139/139 locally), so it is test-isolation/ordering pollution within the shard, not a product-code defect. TestReadTeamActivity was added by bc6bd576459 (#104535).

Django Tests Pass / Django Tests Pass on Depot are rollup gates over that shard, so they go green once it does. (The GitHub-side relay also logged Depot CI never started ...: no wait job after 15 minutes — re-running the Actions job alone doesn't re-trigger Depot, so that one needs a fresh run rather than a rerun.)

I've deliberately not fixed the signals test isolation here, since it belongs to another product and this PR carries a ClickHouse migration, which the repo guidance wants kept narrow. Happy to spin that fix out separately if you'd like — just say the word.

🦉 via talyn.dev

lricoy commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

CI is green — the signals shard failure was a shared test-fixture leak

The Product tests (signals (2/2), field-notes, events schema legacy) shard has been failing here (and intermittently on master) with 34 errors of the form TeamScopeError: Team <id> not found on default DB, raised out of the autouse _scout_team_scope fixture in products/signals/backend/test/conftest.py.

It is not a signals bug. base_test_mixin_fixture in posthog/conftest.py called setUpTestData(), which is a classmethod, so the organization, project, team and user it created were set on PostHogTestCase itself. The rows roll back when that test ends, but the class attributes stay for the rest of the pytest process. Every later class that sets its data up per test (CLASS_DATA_LEVEL_SETUP = False, which ClickhouseTestMixin sets) then reads a deleted team before its own setUp runs — which is exactly what the signals fixture does at setup time.

That is why the shard passed or failed depending on how pytest-split happened to divide the files: it only breaks when a test using that fixture lands in the same process, ahead of a ClickhouseTestMixin class.

Fixed in da97f7e by setting the data up on the instance instead, which is what the team and user fixtures already read.

Verified locally on the full products/signals/backend/test suite:

  • before: 5 failed, 4159 passed, 34 errors
  • after: 5 failed, 4193 passed — all 34 errors gone

The 5 remaining local failures are TestSignalReportDeleteAPI, which needs a Temporal server this sandbox does not run (Failed client connect ... 127.0.0.1:7233). They pass in CI and were never part of the CI failure set.

Also merged master in, so the branch is up to date.

Status now: every check passes (173 success, 98 skipped, 0 failing) and the branch is mergeable and not behind. One test, Event Pipeline E2E tests ... $identify is run concurrently, failed once and passed on retry — it is in the Node event pipeline, which this branch does not touch.

Two things for a human:

  1. This PR now carries a change outside its own area — the one-line posthog/conftest.py fix above. It is test-only, but say the word and I will pull it into its own PR and rebase this one on top.
  2. The approval is stale. @andyzzhao approved at dba2e5ce, before the rebase; GitHub now reports REVIEW_REQUIRED. This needs a fresh approval before it can be queued — I have not enqueued anything.

🦉 via talyn.dev

lricoy commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Re the outside-diff note from @greptile-apps ("Migration includes application changes" — asking that the ClickHouse migration ship on its own, ahead of the HogQL and ad-ID changes): not splitting this one.

The migration recreates raw_sessions_v3 precisely because the column set changes — entry_ad_ids_map and flag_keys go away and the ad-ids set becomes key=value pairs. The DDL it runs is generated from posthog/models/raw_sessions/sessions_v3.py and the ad-id catalog in posthog/raw_sessions_v3_ad_ids.py, and the HogQL table in posthog/hogql/database/schema/sessions_v3.py reads the same catalog. Landing the migration first would mean landing DDL whose source of truth is not in the tree yet; landing the schema module first would put a table definition on master that does not match the deployed table. They describe one shape and have to move together.

The narrow-migration guidance is about not bundling unrelated work with a migration, and there is none here — the diff is the new columns, the code that generates them, the code that reads them, and their tests.

🦉 via talyn.dev

@greptile-apps

greptile-apps Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

That context is fair, and I agree this is a single coordinated schema change rather than unrelated application work. The migration’s DDL, raw-session model/catalog, and HogQL reader must describe the same entry_ad_ids_set shape; landing any one separately would create a period where the source of truth and deployed/read schema disagree. Given that raw_sessions_v3 is being recreated and has no serving consumers yet, the usual migration-isolation concern does not apply here. I’m withdrawing the outside-diff finding.

@lricoy
lricoy force-pushed the lricoy/sessions-v3-more-ad-ids branch from 0f26fe9 to 4fcb56c Compare September 25, 2026 10:04

lricoy commented Oct 22, 2026

Copy link
Copy Markdown
Member Author

Conflict cleared — ClickHouse migration renumbered 0359 → 0360. CI green on 8debe15f.

master landed 0359_logs_ingest_to_apm, which claimed the same number as this branch's 0359_sessions_v3_more_ad_ids. max_migration.txt was the only conflict. Rebased onto master, renamed the migration to 0360_sessions_v3_more_ad_ids.py, and force-published with the signed rewrite.

The diff is unchanged otherwise: git diff --numstat origin/master...HEAD is byte-identical to the pre-rebase diff apart from the rename, still 39 files, all sessions v3. posthog/clickhouse/hcl/check.sh exits 0 locally.

Two CI failures showed up on the new head and both have since passed on re-run. Neither was caused by this branch:

  • Cloud compose gate — COMPOSE MISMATCH on prod-us-apm and prod-eu-apm, missing kafka_logs_avro, writable_logs34 and kafka_logs34_avro_mv. This branch touches no apm or logs file. chore(clickhouse): run logs kafka ingest on ingestion-apm in prod #122283 landed those objects on master at 20:05 UTC and the matching cloud-infra layer landed at 20:29, so every PR in the repo failed this gate in between. Re-ran after the cloud-infra side landed; green.
  • Playwright E2E — 48 failures spread across product analytics, shared dashboards, shared insights, shared notebooks, webhooks, workflows and surveys, all of them beforeAll hook timeouts in playwrightSetup.createWorkspace, not assertion failures. Migration 0360 applied cleanly in that run, the workspace setup path has no sessions dependency, this branch has no frontend or E2E files, and other PRs passed Playwright in the same window. Re-ran once per the flake policy; green. The underlying workspace-setup timeout is harness-wide and outside this PR's scope.

Still waiting on the CODEOWNERS review from @PostHog/clickhouse — that is the only thing holding mergeStateStatus at BLOCKED. No merge has been attempted.

🦉 via talyn.dev

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not approved yet — waiting on the conditions below.

Re-add the stamphog label to request another review once you have addressed this.

stamphog declined to auto-review this pull request because three gates failed. The deny-list gate matched the migrations path, because the PR adds posthog/clickhouse/migrations/0360_sessions_v3_more_ad_ids.py and edits 0325_sessions_v3_flag_key_values.py. The size gate found 2941 substantive lines against an 800-line ceiling (4453 lines across 39 files including docs, generated files and snapshots). The tier gate classified it as T2-never because it spans two areas and is a feature.

A human reviewer needs to look at this one. To make review easier, you could split it into smaller pull requests, for example the migration and table-shape changes separately from the new ad click ID tracking and the HogQL read changes.

  • 👍 on the PR from greptile-apps[bot].
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✗ matches: migrations
size ✗ too large for auto-review (2941L substantive in global — ceiling is 800L; 2941L, 31F total, 4453L/39F incl. docs/generated/snapshots)
tier ✗ classified as T2-never: T2-never (4453L, 39F, two-areas, feat)
stamphog 2.6.1 .stamphog/policy.yml @ unknown · reviewed head 8debe15

@lricoy
lricoy force-pushed the lricoy/sessions-v3-more-ad-ids branch from 8debe15 to 9ace3aa Compare October 23, 2026 20:45

@posthog posthog Bot left a comment

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.

PostHog Review · Standard · reviewed 9ace3aa: 1 Should fix. This pull request is large, so the review ran in 4 parts with less depth than usual.

This is review round 4 on this pull request. When findings keep coming back in the same code, the approach is usually the problem. Before the next fix, check whether a different design would remove the whole class of findings.

lricoy commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

Conflict cleared — ClickHouse migration renumbered 0360 → 0361. CI green on 9ace3aa2.

master landed 0360_metrics4_samples_service_freshness_projection, which claimed the same number as this branch's 0360_sessions_v3_more_ad_ids. git_signed_merge could not auto-merge, so the rebase flow ran; posthog/clickhouse/migrations/max_migration.txt was the only conflicted file.

What changed, and nothing else:

  • 0360_sessions_v3_more_ad_ids.py → 0361_sessions_v3_more_ad_ids.py. The body is byte-identical to the pre-rebase blob (verified with diff against 8debe15f), and the migration carries no reference to its own number.
  • max_migration.txt → 0361_sessions_v3_more_ad_ids. master's 0360_metrics4_… file is untouched, and no duplicate numbers remain in the 03xx sequence.

Verified before publishing: the PR owns the same 39 files before and after, differing only by that rename, with every per-file changed-line count unchanged; origin/master is an ancestor and the branch is 0 commits behind; posthog/clickhouse/hcl/check.sh exits 0 with every (env, role) stack reporting no differences and sql up to date.

One new review finding, declined with reasoning (thread): a P2 asking for a SYSTEM FLUSH DISTRIBUTED drain before the cloud writable drop. The hazard it describes is real, but the plain drop is the established pattern here (0146, 0158, 0175, 0201, 0202, 0206, 0215, 0236 — 0158 being this table's own previous remake), SYSTEM FLUSH DISTRIBUTED appears nowhere in the repo, and a blocking flush would stall the migration in precisely the satellite-outage case it is meant to guard. Left for @PostHog/clickhouse to overrule if they want a drain step.

Current state: MERGEABLE, 224 checks passing, 0 failing, all review threads resolved. The only thing holding mergeStateStatus at BLOCKED is the standing CODEOWNERS review already requested above. No merge has been attempted.

🦉 via talyn.dev

@lricoy
lricoy force-pushed the lricoy/sessions-v3-more-ad-ids branch from 9ace3aa to 50eba1a Compare October 27, 2026 09:23

lricoy commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

Rebased this branch onto current master to clear the merge conflict, and force-pushed the result.

The only conflict was posthog/clickhouse/migrations/max_migration.txt: master landed 0361_logs34_internal_source, so this PR's migration was renumbered 0361_sessions_v3_more_ad_ids → 0362_sessions_v3_more_ad_ids and max_migration.txt updated to match. Nothing else in the diff changed — the per-file numstat against master is identical to before the rebase apart from that rename. posthog/clickhouse/hcl/check.sh exits 0, and CI is green on 50eba1a.

Still waiting on the code-owner review (ClickHouse team) before this can be enqueued.

🦉 via talyn.dev

@lricoy
lricoy force-pushed the lricoy/sessions-v3-more-ad-ids branch from 50eba1a to f43722a Compare October 28, 2026 16:58
lricoy and others added 10 commits October 28, 2026 17:28
Track lower-tier ad click ids in raw_sessions_v3 as a flat 'key=value'
set with a URL query-param fallback, exposing each as an $entry_<id> and
$entry_has_<id> session property. Rework the v3 pipeline: drop
entry_ad_ids_map and flag_keys, switch the uniqExact states to uniq, and
replace the flag key-values bloom filter with a tokenized text index.
The v3 sessions MV read $geoip_subdivision_city_name, which ingestion
never sets (geoip enrichment writes $geoip_city_name), so the
geoip_subdivision_city_name column was NULL on every row. Read
$geoip_city_name instead; the column name is kept to match sessions v2.
The URL fallback could persist bytes that are not valid UTF-8. decodeURLComponent
turns '%FF' into a raw 0xFF byte, and left() counts bytes, so it can also cut a
multi-byte character in half. Once such a value lands in entry_ad_ids_set, every
reader that expects text fails on that session: the /sessions/values picker
decodes the value with json.loads, which raises UnicodeDecodeError rather than
the JSONDecodeError it handles.

Wrap the truncation in toValidUTF8 so the stored value is always text, and
regenerate the checked-in HCL, golden SQL and schema snapshots.

Generated-By: PostHog Desktop
Task-Id: c0a67d0e-1f50-4281-be1a-26a2f1f64baa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The URL fallback used decodeURLComponent, which leaves a query-string '+'
alone. The SDK decodes the same parameter with '+' as a space before promoting
it to an event property, so a value like utm_id=spring+sale was stored as
'spring+sale' from the URL and 'spring sale' from the property, splitting one
campaign across two attribution values.

decodeURLFormComponent follows rfc-1866 and decodes '+' as a space, so both
paths now agree. Regenerates the HCL goldens and schema snapshots, and adds a
regression case covering a plus-encoded space in a URL ad id.

Generated-By: PostHog Desktop
Task-Id: c0a67d0e-1f50-4281-be1a-26a2f1f64baa
Migration 0325 ended by recreating raw_sessions_v3_v from the shared view
builder. That builder now merges uniq states instead of uniqExact, so on an
install whose last applied ClickHouse migration sits between 0158 and 0325 the
statement runs against a table that still holds uniqExact states and aborts the
upgrade:

  Code: 43. Illegal type AggregateFunction(uniqExact, Nullable(UUID)) of
  argument for aggregate function with Merge suffix

Freezing a historical copy of the view SQL does not work, because a fresh
install builds the table from today's SQL at 0158 and would then need the
opposite merge function at 0325.

The recreation is redundant on every path that still reaches it: 0158 already
creates the view from the same source a fresh install uses, and 0354 recreates
it after rebuilding the tables. Dropping the operation leaves the final schema
unchanged and lets the older install reach 0354.

Generated-By: PostHog Desktop
Task-Id: c0a67d0e-1f50-4281-be1a-26a2f1f64baa
@lricoy
lricoy force-pushed the lricoy/sessions-v3-more-ad-ids branch from f43722a to e5cf4fa Compare October 28, 2026 20:29

lricoy commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

/depot retry

Retrying the Depot jobs that failed on an infrastructure timeout (Install uv aborted), not on a test failure.

🦉 via talyn.dev

@lricoy lricoy changed the title feat(sessions): track more ad click ids in sessions v3 with URL fallback feat(sessions): ship the sessions v3 schema with more ad click ids Oct 29, 2026
Migrations 0180 and 0325 replay on a fresh cluster and add flag_keys plus the
flag_keys and flag_key_values bloom filter indexes on top of today's base SQL,
which no longer declares any of them. A fresh install therefore ended up with a
raw_sessions_v3 the declared HCL does not describe, and the multinode migration
smoke reported the drift.

Drop all three in the non-cloud branch of 0364, before the materialized views
are created. Cloud is unaffected: that branch is still empty, because the
ClickHouse team applies the rebuild by hand.

Generated-By: PostHog Desktop
Task-Id: c0a67d0e-1f50-4281-be1a-26a2f1f64baa
@trunk-io
trunk-io Bot merged commit 367aabc into master Oct 30, 2026
392 of 440 checks passed
@trunk-io
trunk-io Bot deleted the lricoy/sessions-v3-more-ad-ids branch October 30, 2026 15:56
@deployment-status-posthog

deployment-status-posthog Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-10-30 16:51 UTC Run
prod-us ✅ Deployed 2026-10-30 17:17 UTC Run
prod-eu ✅ Deployed 2026-10-30 17:11 UTC Run

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

Labels

ci-backend-github Keep this PR backend tests on GitHub Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants