Skip to content

fix(workflows): keep one mail from label per email sending domain - #127298

Closed
Silthus wants to merge 14 commits into
email-domain-agent-setupfrom
email-mail-from-label
Closed

Silthus wants to merge 14 commits into
email-domain-agent-setupfrom
email-mail-from-label

Conversation

@Silthus

@Silthus Silthus commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Note

Closed, superseded by #125535. That PR covers one MAIL FROM label per domain for new senders. Once it lands, a small follow-up may bring back the web app change that sends the label only when the person edits it.

Moved from Silthus#569. It first opened as #116864.

Problem

  • Two Workflows email senders on one domain can quietly undo each other's MAIL FROM subdomain (the Return-Path label, for example feedback).
  • SES stores that label on the domain identity, so every sender on the domain shares one label.
  • A new sender with a different mail_from_subdomain re-sets the label in SES for every sender on the domain.
  • Editing the label on one sender stores it on that sender only. The next verify of any other sender re-sets SES to that sender's old label.

This recreates the fork draft Silthus/posthog#345 on top of #127296. Its earlier upstream draft, #111367, is closed.

Impact

Pays into Grow Workflows through organizations that send from a verified domain: a sender whose MAIL FROM label moves under it no longer matches the DNS records its owner published. Not measured on its own; it guards the email domain verified path of #127296. Expected move: none on its own. Run cost: n/a.

Caution

🔴 Merge risk: high · two-way door (merge-risk rubric)

  • With workflows-email-from-code on, an edit rewrites the shared delivery setting of every sender on the domain in the organization, other projects included, and moves SES with it. A wrong label breaks the domain's MAIL FROM until its DNS records match.
  • Create, edit and verify hold a per-domain Postgres advisory lock across SES calls, and an edit takes row locks in id order.
  • The flag is off by default, and with it off the three paths keep master's logic, including the inline worker reload. The flag-off additions are one flag evaluation, one Team lookup on create, and the label left out of name-only saves in the web app.

Changes

Everything below runs only when the workflows-email-from-code flag is on, the flag that also gates the agent sender tools in #127296.

  • A new sender takes the domain's existing label when the request omits it.
  • A new sender that asks for a different label gets a 400 that names the label in use. SES is not called.
  • When senders on a domain already have different labels, a new sender gets a 400 that says to save one label on an existing sender first.
  • Editing the label on one sender writes it to every sender on that domain in the organization, so a later verify of any of them keeps the new label.
  • An edit also moves SES when another sender on the domain holds a different label. That repairs a domain whose senders disagree when the person saves one label, as the 400 asks.
  • An edit with a blank label keeps the current label.
  • Create, edit and verify each run in one transaction under a per-domain advisory lock, and re-read the sender under it. A verify that loaded its sender before an edit cannot re-set the old label.
  • An edit locks the domain's senders, the edited one included, in one SELECT ... FOR UPDATE OF posthog_integration ... ORDER BY id. That is the order a verification locks them in, so a flag-off verification and a flag-on edit cannot deadlock. No Team row is locked.
  • The worker reload after an edit or a successful verify runs on commit, so workers never reload a sender before its row commits. With the flag off it runs inline, as on master.
  • Mechanical: one constant holds the default label, and one helper next to EmailIntegration evaluates the flag for both the API gate and the model. It sends the web app's person and group inputs (flag_identity), so the project's id property is the team id, not its UUID. The status route's check of workflows-email-domain-wizard uses the same inputs. Domain Connect reads the label through EmailIntegration.

Warning

Roll the flag out by organization. A teammate with the flag off still edits only their own sender, and their verify can re-set the old label for the domain.

The setup modal pre-fills feedback. On a domain that already uses another label, the user sees the 400 in a toast and edits the field. The sender page and the Configure modal send the label only when the person edits it. No screen looks different.

Read first: EmailIntegration._domain_guard, _lock_edited_sender and create_native_integration in posthog/models/integration/email.py.

How did you test this code?

Lane gate on the head, 2026-11-02: ruff, the touched tests on a private database, the MCP tests and typecheck, repo-wide mypy and the full frontend typecheck, all green. Both tach passes CI runs are green on the head merged into current master.

CI report: the bundle-size and Python and TypeScript duplication rows name master files this PR does not touch, such as warehouse sources and activity describers. The report compares against the stacked base branch, which is behind master.

Test seams: the public methods of EmailIntegration (create_native_integration, update_native_integration, verify) and the email edit endpoint. The SES facade and posthoganalytics.feature_enabled are mocked as external boundaries.

Test rationale:

  • test_new_sender_keeps_the_domain_mail_from_label and test_new_sender_cannot_move_the_domain_mail_from_label: catch the create path, including a legacy blank label. No existing test sent a label on create.
  • test_verifying_any_sender_keeps_a_changed_mail_from_label: catches the edit-then-verify path through instances loaded before the edit. It fails without the sibling write and without the re-read under the lock.
  • test_editing_a_sender_without_a_label_keeps_the_current_domain_label: catches a name-only edit that re-sets a label changed since the sender loaded.
  • test_each_sender_keeps_its_own_mail_from_label_while_the_flag_is_off: pins master's behavior with the flag off.
  • test_editing_the_label_shares_it_when_the_flag_is_on_for_the_requesting_user: sends a real PATCH and fails if the view stops passing the request user.
  • test_saving_a_label_a_sibling_lacks_moves_ses_to_it (resent label, name-only edit): fails if SES stays on a sibling's label while the rows move. Red before d6080d93ad.
  • leaves the MAIL FROM label out when saving the sender name / recording the setup method (emailDomainLogic.test.ts) and leaves out an unchanged MAIL FROM label (emailSetupModalLogic.test.ts): fail if a name-only save resends a stale label. Red before d6080d93ad and 1a311bf509.
  • test_flag_released_to_a_project_id_reaches_the_sender_update (from #127296): fails if the flag check stops sending the project's numeric id.
  • TestEmailIntegrationConcurrentLabelEdits.test_a_shared_label_edit_waits_for_a_lower_sender_before_locking_its_own_row (TransactionTestCase, three connections): one connection holds the lower-id sender, the edit of the higher-id sender waits on a row lock, and a NOWAIT lock of the edited row from a third connection must succeed. Red before dc05208509: the edit already held its own row.

Lane gate on 48c043fd9c: the integration API, integration model, email status and eval tests (1,243 passed); the MCP unit tests (5,363 passed); the EmailDomain and EmailSetup frontend tests (105 passed); the frontend typecheck; mypy and ruff on the changed Python. Before the last merge, the wider sweep with products/workflows/backend and Domain Connect passed except test_ses_provider.py::test_delete_identity_removes_tenant_associations_first, a master test outside this diff that fails in that ordering on the pre-fix head as well and passes alone.

Proofs per qa-swarm PROOF.md:

  • Live run: gap. It needs a real SES account and a domain under DNS control. The lock order test runs real concurrent transactions, but not the SES calls.
  • Prod shape: gap, owed: it runs once read-only production access is in place.
  • Shadow diff: gap. Comparing base and head label edits needs SES and real sender rows. The model tests run the flag-off and flag-on paths over the same sender rows.
  • Post-deploy watch: owed after merge.

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Release status

  • This change is behind a feature flag and is not available to users

Docs update

None. No doc under docs/ covers the email sender MAIL FROM setting.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Claude Opus 5.5 (claude-opus-5-5)

Decisions

Question Answer Who Where
Which flag gates the shared label? workflows-email-from-code, the one rollout flag for the Workflows email work 👤 Human map
Does the label rule follow the flag per acting user or per project? Per acting user, like the MCP tools and the API gate; the rollout warning limits it to organization targeting 🤖 Agent pending approval
How does a shared label edit lock the senders? All of them in id order in one query, matching the verification's order; the flag-off edit keeps master's single-row lock 🤖 Agent pending approval
Does an edit on a mixed-label domain move SES? Yes, to the saved label, and an SES error fails the edit before any row changes 🤖 Agent pending approval
Do name-only saves send the label? No; the web app sends it only when the person edits it 🤖 Agent pending approval

Involvement: no grilling rounds, no prototypes. GPT-6.1 Sol (high) reviewed the restack in round 1 (one finding, fixed) and in four final reads (one deadlock finding, fixed). On 2026-11-01 a two-family panel reviewed the whole diff: nine GPT-6.1 Sol (high) lenses and a Claude Opus 5.5 (high) fresh-eyes lane. Round 2 raised 5 distinct findings: 3 fixed, 1 rejected, 1 follow-up. Round 3 checked the fixes: 5 findings, 3 fixed, 2 rejected. Final reads follow. Proof: command output; live run and shadow diff owed.

More context for AI agents

Revisions on the fork, before the move:

  • Restacked on #127296, which now sits on master 43333f81da9. Master's memoized workflows config is now an extension per Team instance, so this PR no longer clears it on commit and no longer touches default_email_sender.py.
  • The flag is workflows-email-from-code, the one rollout flag for the Workflows email work.
  • Review fix dc05208509: a shared label edit locks every sender of the domain, the edited one included, in id order. Before, it locked its own row first and could deadlock against a verification.
  • Two-family panel fixes, 2026-11-01:
    • d6080d93ad: an edit moves SES to the chosen label when another sender on the domain holds a different one, even if the edited sender already stores it.
    • d6080d93ad, 1a311bf509: the sender page's name and setup-method saves, and the Configure modal unless its label changed, leave the label out. A page loaded before a label edit can no longer revert it.
  • Merged #127296 at 641380bd8c (4064cb8b50, 4f59ef47cd). The email flag helper in posthog/models/integration/email.py now builds its inputs with flag_identity.

🤖 Generated with Claude Code

Silthus and others added 7 commits November 1, 2026 13:17
With workflows-email-from-code on, a new sender takes the domain's
existing MAIL FROM label, an edit shares the label with every sender on
the domain, and create, edit and verify hold a per-domain advisory lock.
Worker reloads wait for that lock's commit. The flag helper now lives
next to EmailIntegration so the API gate and the model evaluate it the
same way.

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

The edit locked its own sender, then the other senders of the domain.
A verification on the same domain locks them all in id order, so the
two could deadlock. The edit now locks every sender of the domain,
its own included, in one query ordered by id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The email flag helper now lives in posthog/models/integration/email.py and
builds its inputs with flag_identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- an edit that saves the label the edited sender already stores still moves
  SES to it when another sender on the domain holds a different label, since
  SES may hold that other label
- the sender page's name and setup-method saves leave the label out, so a
  page loaded before a label edit cannot revert the domain to its old label

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

The Configure modal resent the label it loaded, so a save made after
another sender changed the domain label reverted it.

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

@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 48c043f: 1 Must fix, 1 Should fix.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.

Comment on lines +279 to +281
for sender in domain_senders:
sender.config["mail_from_subdomain"] = mail_from_subdomain
sender.save(update_fields=["config"])

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.

P1 · Authorize access to every project before updating shared senders

With the flag enabled, a token scoped to project A can PATCH its sender and rewrite mail_from_subdomain on matching senders in project B. Endpoint permissions check only the URL's project, while _lock_edited_sender selects the whole organization. This bypasses credential scope and project permissions; authorize every affected project before changing SES or rows.

Comment on lines +197 to +200
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute("SELECT pg_advisory_xact_lock(hashtextextended(%s, 0))", [f"email-domain:{domain}"])
yield

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.

P2 · Bound domain lock waits and the SES calls made under the lock

With the flag enabled, slow SES calls hold this transaction and domain lock through SDK retries using default 60-second socket timeouts. Concurrent creates, edits, and verifications for the domain then block here, each retaining a database connection and request worker. Bound lock acquisition and provider execution, and return a retryable response when the budget expires.

@github-actions

github-actions Bot commented Nov 2, 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.

✅ Complexity (TypeScript) — clean

Cyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions.

⚠️ Duplication (Python) — 3 new duplicated blocks (worst 106 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
products/warehouse_sources/backend/temporal/data_imports/sources/calendly/calendly.py:31 products/warehouse_sources/backend/temporal/data_imports/sources/ramp/ramp.py:14 14 106
products/warehouse_sources/backend/temporal/data_imports/sources/drata/drata.py:334 products/warehouse_sources/backend/temporal/data_imports/sources/ramp/ramp.py:239 18 73
products/warehouse_sources/backend/temporal/data_imports/sources/ramp/ramp.py:6 products/warehouse_sources/backend/temporal/data_imports/sources/zuora/zuora.py:5 11 71
⚠️ Duplication (TypeScript) — 4 new duplicated blocks (worst 180 tokens)

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.

First copy Second copy Lines Tokens
services/mcp/scripts/generate-tools.ts:2281 services/mcp/scripts/generate-tools.ts:2306 20 180
services/mcp/scripts/generate-tools.ts:2260 services/mcp/scripts/generate-tools.ts:2288 12 138
services/mcp/scripts/yaml-config-schema.ts:188 services/mcp/scripts/yaml-config-schema.ts:624 30 100
services/mcp/scripts/yaml-config-schema.ts:199 services/mcp/src/tools/toolDefinitions.ts:22 19 81
⚠️ Bundle size — 🔺 +2.7 KiB (+0.0%)

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

Total: 76.21 MiB · 🔺 +2.7 KiB (+0.0%)

File Size Δ vs base
posthog-app/_parent/products/signals/frontend/inbox/InboxScene.js 506.1 KiB 🔺 +1.3 KiB (+0.3%)
posthog-app/src/lib/components/CommandKSearch/CommandKSearch.js 52.5 KiB 🔺 +1.1 KiB (+2.2%)

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

✅ Eager graph — within 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.01 MiB · 25 files no change █████████░ 88.1% 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
2.98 MiB · 626 files 🔺 +125 B (+0.0%) ██████████ 98.4% of 3.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
4.00 MiB · 1,275 files 🔺 +125 B (+0.0%) ██████████ 99.7% of 4.01 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.46 MiB · 3,719 files 🔺 +125 B (+0.0%) ██████████ 96.2% of 9.84 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
4.92 MiB · 1,794 files 🔺 +125 B (+0.0%) ██████████ 99.7% of 4.93 MiB
AI-first home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
6.18 MiB · 2,289 files 🔺 +125 B (+0.0%) ██████████ 98.6% of 6.27 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
8.87 MiB · 3,441 files 🔺 +125 B (+0.0%) █████████░ 94.2% of 9.41 MiB
web analytics scene
src/scenes/web-analytics/WebAnalyticsScene.tsx
9.67 MiB · 3,718 files 🔺 +125 B (+0.0%) █████████░ 92.1% of 10.49 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
7.38 MiB · 2,678 files 🔺 +125 B (+0.0%) █████████░ 90.8% of 8.13 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 products/product_analytics/frontend/insights/activityDescriptions 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/lib/logic/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/kea-test-utils/ 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
🟢 products/product_analytics/frontend/insights/activityDescriptions stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/kea-test-utils/ 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
🟢 src/layout/panel-layout/ProjectTree/menus/SessionReplayMenuItems.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/navigation/projectNoticeLogic.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/navigation/ImpersonationNotice/ImpersonationNotice.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/cohorts/cohortSavedToast.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
🟢 products/subscriptions/frontend/components/Subscriptions/exportNudge/exportNudgeLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/billing/paymentEntryLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/re2js/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/react-markdown/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/Command/Command.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 packages/quill/packages/ 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/queries/web-analytics-validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/re2js/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/kea-test-utils/ 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/queries/web-analytics-validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/re2js/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/kea-test-utils/ 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/queries/validators.js stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/re2js/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/kea-test-utils/ 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
🟢 node_modules/re2js/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/d3/src/ 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.9 KiB src/lib/utils/bootTiming.ts
1.7 KiB src/index.tsx
1.7 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
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
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
104.0 KiB src/products.tsx
89.0 KiB src/lib/api.ts
68.7 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
28.3 KiB src/lib/constants.tsx
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
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
104.0 KiB src/products.tsx
89.0 KiB src/lib/api.ts
68.7 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
32.7 KiB src/lib/utils/eventUsageLogic.ts
28.6 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
239.9 KiB ../packages/quill/packages/quill/dist/index.js
224.1 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
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
104.0 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
89.0 KiB src/lib/api.ts
84.9 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/surveys-preview.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
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
239.9 KiB ../packages/quill/packages/quill/dist/index.js
224.1 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
104.0 KiB src/products.tsx
89.0 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.0 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
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
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
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
239.9 KiB ../packages/quill/packages/quill/dist/index.js
224.1 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
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
104.0 KiB src/products.tsx
89.0 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.0 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
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
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
239.9 KiB ../packages/quill/packages/quill/dist/index.js
224.1 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
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
104.0 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
89.0 KiB src/lib/api.ts
76.6 KiB src/scenes/dashboard/dashboardLogic.tsx
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
Size File
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
239.9 KiB ../packages/quill/packages/quill/dist/index.js
224.1 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
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
104.0 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
89.0 KiB src/lib/api.ts
84.9 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/surveys-preview.js
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
325.3 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
320.2 KiB ../node_modules/.pnpm/posthog-js@1.444.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
239.9 KiB ../packages/quill/packages/quill/dist/index.js
224.1 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
104.0 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
89.0 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx

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

✅ Frontend layers — no new upward imports, 0 removed

Static imports from a lower frontend layer into a higher one (layers, lowest first: kernel, shell, query, features). Compared with the base branch, so an import that a PR also adds to layer-baseline.txt still shows here. Sizes are the source bytes on the eager path of every logged-in page.

Layer Eager source bytes Δ vs base
kernel 2.96 MiB 🟢 -846 B (-0.0%)
shell 2.38 MiB 🔺 +175 B (+0.0%)
query 56.4 KiB no change
features 534.0 KiB no change

Download the interactive layer graph for this PR.

Posted automatically by check-layers

✅ Toolbar bundle — eager 2.34 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.34 MiB · 19 files no change ████░░░░░░ 40.9% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 42 files no change 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
944.9 KiB dist/toolbar/toolbar-app-5IRQLJNF.css
696.3 KiB dist/toolbar/chunk-chunk-XWJRZSYO.js
261.2 KiB dist/toolbar/chunk-chunk-FAKTR4DZ.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
130.3 KiB dist/toolbar/chunk-chunk-W4NDZLFP.js
75.7 KiB dist/toolbar/toolbar-app-JXPIQSLE.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
33.8 KiB dist/toolbar/chunk-chunk-E6TIOVF5.js
21.7 KiB dist/toolbar/chunk-chunk-XLJAXYNC.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 — 🔺 +12.3 KiB (+0.0%)

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

Total: 970.37 MiB · 🔺 +12.3 KiB (+0.0%)

ℹ️ MCP UI apps size — 35 app(s), 29120.9 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 844.3 KB 211.6 KB
explain-chart 1117.7 KB 211.6 KB
action 767.7 KB 211.6 KB
action-list 835.3 KB 211.6 KB
cohort 766.7 KB 211.6 KB
cohort-list 834.3 KB 211.6 KB
email-template 766.6 KB 211.6 KB
error-details 771.6 KB 211.6 KB
error-issue 768.9 KB 211.6 KB
error-issue-list 836.8 KB 211.6 KB
experiment 832.4 KB 211.6 KB
experiment-list 836.1 KB 211.6 KB
experiment-results 834.3 KB 211.6 KB
feature-flag 834.7 KB 211.6 KB
feature-flag-list 838.4 KB 211.6 KB
feature-flag-testing 770.0 KB 211.6 KB
inline-scan 767.2 KB 211.6 KB
insight-actors 833.8 KB 211.6 KB
llm-costs 831.1 KB 211.6 KB
session-recording 769.0 KB 211.6 KB
survey 768.3 KB 211.6 KB
survey-global-stats 831.1 KB 211.6 KB
survey-list 836.0 KB 211.6 KB
survey-stats 831.1 KB 211.6 KB
trace-span 767.1 KB 211.6 KB
trace-span-list 835.2 KB 211.6 KB
vision-observation-list 834.4 KB 211.6 KB
workflow 767.1 KB 211.6 KB
workflow-list 834.7 KB 211.6 KB
workflow-setup-status 766.7 KB 211.6 KB
query-results 1089.6 KB 211.6 KB
render-ui 1193.6 KB 211.6 KB
visual-review-snapshots 771.6 KB 211.6 KB
workflow-data-suggestions 769.4 KB 211.6 KB
workflow-sending-rules 768.1 KB 211.6 KB

@Silthus Silthus closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant