Repository navigation
Conversation
🤖 CI report✅ Complexity (TypeScript) — cleanCyclomatic 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. ✅ Bundle size — no changeUncompressed size of every built Total: 76.03 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report
|
| Root | Eager (shipped) | Δ vs base | Budget |
|---|---|---|---|
entry (logged-out pages, app bootstrap)src/index.tsx |
1.01 MiB · 24 files | no change | █████████░ 88.6% 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.94 MiB · 620 files | no change | ██████████ 96.9% of 3.03 MiB |
🟡 authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
7.65 MiB · 2,633 files | no change | ██████████ 102.1% of 7.50 MiB |
dashboard scenesrc/scenes/dashboard/Dashboard.tsx |
9.68 MiB · 3,703 files | no change | ██████████ 96.7% of 10.01 MiB |
🟡 today home pathsrc/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx |
8.06 MiB · 2,977 files | no change | ██████████ 104.1% of 7.74 MiB |
AI-first home pathsrc/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx |
7.90 MiB · 2,706 files | no change | █████████░ 88.1% of 8.96 MiB |
events scenesrc/scenes/activity/explore/EventsScene.tsx |
9.15 MiB · 3,495 files | no change | ██████████ 95.4% of 9.59 MiB |
web analytics scenesrc/scenes/web-analytics/WebAnalyticsScene.tsx |
10.01 MiB · 3,713 files | no change | █████████░ 92.4% of 10.83 MiB |
replay detail scenesrc/scenes/session-recordings/detail/SessionRecordingDetail.tsx |
8.22 MiB · 3,075 files | no change | █████████░ 86.8% 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
🟢 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
🟢 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.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.4 KiB | src/index.tsx |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 1.3 KiB | src/lib/utils/bootTiming.ts |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
| Size | File |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 69.4 KiB | src/lib/lemon-ui/icons/icons.tsx |
| 38.7 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 |
| 33.9 KiB | ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js |
| 29.4 KiB | src/scenes/scenes.ts |
| 26.9 KiB | src/lib/constants.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
| Size | File |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 93.3 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 |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 93.3 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/ai-first/AiFirstHomepage.tsx
| Size | File |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 93.3 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 |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 93.3 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 |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 183.2 KiB | src/queries/validators.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
| Size | File |
|---|---|
| 321.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.18_@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.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js |
| 286.8 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.7 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 |
| 106.3 KiB | src/products.tsx |
| 98.9 KiB | src/lib/api.ts |
| 93.3 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 PostHog#32479
✅ Toolbar bundle — eager 2.29 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.29 MiB · 18 files | no change | ████░░░░░░ 39.9% of 5.72 MiB |
| Deferred (lazy) | 2.11 MiB · 40 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 |
|---|---|
| 899.4 KiB | dist/toolbar/toolbar-app-REMKF2SV.css |
| 691.4 KiB | dist/toolbar/chunk-chunk-DDSP4M6B.js |
| 260.7 KiB | dist/toolbar/chunk-chunk-HRRIOF34.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-FDH2IBXT.js |
| 130.4 KiB | dist/toolbar/chunk-chunk-IT6XUU73.js |
| 74.8 KiB | dist/toolbar/toolbar-app-YPZZ2PIS.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-TSAL54PB.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-CGDYYVIR.js |
| 21.7 KiB | dist/toolbar/chunk-chunk-PM4VBMO5.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 — 🔺 +453 B (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1020.76 MiB · 🔺 +453 B (+0.0%)
ℹ️ MCP UI apps size — 34 app(s), 27964.5 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 844.2 KB | 209.0 KB |
| action | 767.6 KB | 209.0 KB |
| action-list | 835.3 KB | 209.0 KB |
| cohort | 766.6 KB | 209.0 KB |
| cohort-list | 834.2 KB | 209.0 KB |
| email-template | 766.5 KB | 209.0 KB |
| error-details | 771.5 KB | 209.0 KB |
| error-issue | 768.0 KB | 209.0 KB |
| error-issue-list | 835.8 KB | 209.0 KB |
| experiment | 832.4 KB | 209.0 KB |
| experiment-list | 836.0 KB | 209.0 KB |
| experiment-results | 834.9 KB | 209.0 KB |
| feature-flag | 834.6 KB | 209.0 KB |
| feature-flag-list | 838.4 KB | 209.0 KB |
| feature-flag-testing | 769.9 KB | 209.0 KB |
| inline-scan | 767.1 KB | 209.0 KB |
| insight-actors | 833.7 KB | 209.0 KB |
| llm-costs | 831.0 KB | 209.0 KB |
| session-recording | 768.9 KB | 209.0 KB |
| survey | 768.2 KB | 209.0 KB |
| survey-global-stats | 831.1 KB | 209.0 KB |
| survey-list | 835.9 KB | 209.0 KB |
| survey-stats | 831.0 KB | 209.0 KB |
| trace-span | 767.0 KB | 209.0 KB |
| trace-span-list | 835.1 KB | 209.0 KB |
| vision-observation-list | 834.3 KB | 209.0 KB |
| workflow | 767.0 KB | 209.0 KB |
| workflow-list | 834.6 KB | 209.0 KB |
| workflow-setup-status | 766.7 KB | 209.0 KB |
| query-results | 1086.5 KB | 209.0 KB |
| render-ui | 1161.5 KB | 209.0 KB |
| visual-review-snapshots | 771.5 KB | 209.0 KB |
| workflow-data-suggestions | 769.3 KB | 209.0 KB |
| workflow-sending-rules | 768.0 KB | 209.0 KB |
ℹ️ MCP agent API — agent-facing tool changes
What this PR changes for agents, from the tool schema snapshots and tool definitions.
Tools changed (1):
| Tool | Params | Scopes | Annotations | Schema chars |
|---|---|---|---|---|
workflows-send-email |
+bcc +cc +headers +tags |
description changed | 3,291 -> 4,912 |
ed45d34 to
2585fe8
Compare
9a2b0e1 to
0398172
Compare
A code send on messaging/send/email/ takes cc, bcc, headers and tags. Django checks them and passes them to cdp-api on the accepted send. The worker lays them over the email step, sends headers and tags to SES and headers to maildev, and adds the tags to the sent event. The SES webhook adds the send's own tags to engagement events as $email_tags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodemailer reads a header object with `key` and `value` names as one header, so a send with headers by those names lost the rest, including Auto-Submitted. Pass the headers as a list. The worker checked copies with a stricter address rule than Django, so a copy such as archive=orders@example.com passed Django and then failed to queue. The worker now uses Django's bare-address grammar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d checks A reused key with another cc, bcc, header or tag answers idempotency_key_reused, and a suppressed cc is refused before the 202. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The send checks before the 202 saw only the recipient, so a suppressed cc or bcc passed admission and the worker skipped the send after a 202. Every copy is now a recipient of the admission checks, as it is at delivery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused cc, bcc, header or tag answered with the why of a field PostHog doesn't know. Each now has its own why. The rendered send's expected payload also carries the default sender flag the code form added. 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>
A keyed retry that listed the same headers in another order changed the idempotency fingerprint, so it got a 422 instead of a replay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unknown-field check moves beside the other send field rules, so the tag serializer refuses an unknown key by name as the root body does. 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>
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0398172 to
ba5d72e
Compare
2585fe8 to
6a82b47
Compare
Moved here from #121851, closed on 2026-10-25 so this fork stack is the only copy. It opens upstream again after review.
Problem
cc,bcc, custom headers ortagson Resend, because PostHog's code-form send cannot carry them. The migration never finishes (Silthus/posthog#543).Impact
Pays into Grow Workflows through billed
workflows_emailsactions from orgs that send by code (not measured yet: needs send attribution, #120628, merged and an insight of code sends per org). In scope: Messaging, email API sends. Expected move: small, up. It removes a reason a Resend call site stays on Resend; the larger move comes from the send route and the SDK code form. Run cost: n/a, the same queue and worker. Each cc and bcc address uses one unit of the sending cap, astodoes.Merge Danger
Warning
🟡 Merge risk: medium · two-way door (merge-risk rubric)
workflows-transactional-email-api, and no SDK sends these fields yet.202, as a rollback after the flag turns on would.Changes
What a caller can now do
POST /api/projects/@current/messaging/send/email/takescc,bcc,headersandtags. All four work with rendered content and with a local template by key.template.idrefuses all four with a 400 that says why: a template made in PostHog sends with its own settings.tagsreach the email's engagement events as$email_tags, an object of name to value.$workflows_email_sentand$workflows_email_failedcarry them too.400 recipient_suppressedbefore the202, as a suppressedtodoes, withattrnamingccorbcc. Nothing is queued.Idempotency-Keywith another cc, bcc, header or tag answers422 idempotency_key_reused. The same headers in another order replay.nameandvalueanswers400 invalid_requestontags.whythat explains that field.Limits
detailcc,bccheadersFrom,To,Cc,Bcc,Reply-To,Subject,Date,Message-ID,MIME-Version,Sender,Return-Path,Received,DKIM-Signature,Auto-Submitted, anyContent-*,X-PostHog-*,X-SES-*). A name outside SES's rules (visible ASCII, no colon, 126 characters). A value with line breaks or non-ASCII, or over 870 characters. A name set twice in another casetags[A-Za-z0-9_-], over 256. A name that starts withph_, which PostHog uses. A name used twiceList-Unsubscribe,In-Reply-ToandReferencesstay allowed, so a caller can thread replies and offer its own unsubscribe link.How it works
sendit hands cdp-api, which stores it on the run asstate.send.Destination, headers first inSimple.Headers(PostHog's unsubscribe, tracking andAuto-Submittedheaders follow), and tags afterph_idinEmailTags.202. The worker still skips the whole send on a suppressed address, and each address uses one unit of the sending cap.send, so they are part of the idempotency fingerprint with no fingerprint change.send, so the fingerprint does not depend on the caller's order.Mechanical: the regenerated OpenAPI types and MCP tool-schema snapshots.
Not in this PR: attachments (the next slice), the SDK parameters (lanes Silthus/posthog#536 and Silthus/posthog#537).
How did you test this code?
Test seams:
function_emailstep, for the overlay.EmailServiceagainst a faked SES client and real maildev, and the SES webhook.Test rationale: what each new test catches:
template.id.ph_id. Maildev gets the headers, including ones namedkeyandvalue, which nodemailer would otherwise read as one header.$email_tagsand dropsph_*andses:*tags. The sent event carries$email_tags.archive=orders@example.com, and refuses a copy that names two recipients.422 idempotency_key_reusedfor a key reused with another cc, bcc, header or tag.whythat names its field, not the unknown-fieldwhy.sendfor the same headers, and for the same tags, in two orders.reply_toquoted with a comma.Each new test failed without its production change. Local gate at the stack top
987b927630e, which holds this head unchanged, all green: pytest 1764, CDP jest 3690 (1 skipped), cdp-api serial 288, MCP 5035, repo-wide mypy, nodejstsc, and the rest of the gate. The pre-push preflight ran in strict mode with 0 failures.Live run at
987b927630e(Django, cdp-api and the email worker on a migrated clone of the local test database, delivering to maildev):copy@example.com(and an attachment from #121852)202jane@example.com, cccopy@example.comIdempotency-Keywith another cc422 idempotency_key_reused400 recipient_suppressed,attr: "cc"Earlier live run on
0470814fdd6, before the review fixes: headers, bcc and refusalsThe stored template
password-resetand its workflow existed before the sends, the way the base code writes them.X-Entity-Ref-ID,List-Unsubscribeand a tag202manager@example.com. Envelopejane@,manager@,audit@, and noBccheader. Both custom headers present202manager@example.com, headerX-Entity-Ref-ID: reset-9202template.id400attr: cc, "Not withtemplate.id. A template made in PostHog sends with its own settings."Bcc400attr: headers, "PostHog setsBccitself. Use the send's own fields…"\r\nBcc:400attr: headers, "…printable ASCII characters, with no line breaks."Cross-version (base
ba5d72e8d88, head6a82b47b1b2, real Django body, cdp-api route, workers, webhook and fingerprint in each tree):$email_tagsto a base-sent email.400withattr, base Djangoattrand still returns the rejection.202; onlytogets the email.422instead of the replay.Not checked: real SES. A sandbox SES account was not available to this lane, so SES's handling of the copies, headers and tags, and tags on real engagement events, are unproven. The base-built cross-version values are kept as fixtures in proof storage, not committed as tests. Prod shape: gap, accepted until read-only production access arrives on 2026-11-02.
Release status
Docs update
None. No doc under
docs/covers the send route.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (
claude-opus-5-5)/tdd,/improving-drf-endpoints,/writing-tests,/writing-pr-descriptions,impact,merge-risk..agents/security.mdwas read before the header handling.keyandvaluedropped the other headers, and the worker checked copies more strictly than Django.$email_tagsis one object, not flat properties, so a tag name can never collide with an event property. The worker checks headers again, so an unsafe header fails closed whatever path queued it.attrof a suppressed cc, the "one recipient" copy and thetemplate.idhint for an invalidto. Final read 1 and the rebase check in final read 3 found nothing.🤖 Generated with Claude Code