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 — no change
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1020.76 MiB · no change
ℹ️ 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 |
+attachments |
4,912 -> 5,737 |
ed45d34 to
2585fe8
Compare
6095d91 to
320f9a6
Compare
A code send can attach up to 10 files, 512KB in total: base64 content, a filename, an optional content type and an optional content id to show the file inline. The send route refuses blocked file types, paths and line breaks in names, and unsafe MIME parameters, without repeating the content. The worker checks the attachments again, sends them through SES as one raw MIME message that keeps PostHog's tracking and unsubscribe headers, and attaches them in maildev. Lifecycle snapshots list each file without its content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodemailer read a header map with `key` and `value` names as one header, so a send with attachments and headers by those names went out with a malformed header and without PostHog's tracking and Auto-Submitted headers. Pass the prepared headers as a list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused attachment answered with the why of a field PostHog doesn't know. It now says what attachments PostHog takes. 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file named `.exe` has no suffix to the path parser, so it passed the blocked file type check. The extension is now the text after the last dot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An attachment with `contentType` or `cid` got a 202 and went out without them. Attachments now refuse an unknown key by name, as tags do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ters The email provider takes at most 78 characters for each, so a longer one got a 202 and then failed at send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lifecycle snapshot lists attachments without their content, and a rerun restored that send as is, so its lifecycle row threw and failed the whole page. The snapshot now marks the omission, and the rerun skips that row with a warning, as it does for a sensitive email. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t caps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
320f9a6 to
b41402a
Compare
2585fe8 to
6a82b47
Compare
Moved here from #121852, closed on 2026-10-25 so this fork stack is the only copy. It opens upstream again after review.
Problem
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. Run cost: SES's attachment charge per GB sent, small at a 512 KB cap. The same queue and worker otherwise.Merge Danger
Warning
🟡 Merge risk: medium · two-way door (merge-risk rubric)
Attachmentslist on its simple message. A wrong mapping would change a file's name, type or inline placement.workflows-transactional-email-apiwith no SDK sending attachments yet.202, as a rollback after the flag turns on would.Changes
What a caller can now do
attachments: up to 10 files, 512 KB in total after decoding. Each hasfilename, base64content, an optionalcontent_typeand an optionalcontent_id.content_idshows the file inline, where the HTML references it ascid:<content_id>.template.idrefusesattachments, like the fields of the slice below..exe, also as the whole name), a filename with a path or line break, acontent_typethat is not a MIME type, and acontent_idwith<or>. No error repeats the content.content_typeandcontent_idtake at most 78 characters, the SES limit, so a longer value answers400instead of failing at SES after the202.contentType, answers400 invalid_requestonattachments. Before, the route dropped it, and an invite could lose itsmethod=REQUEST.whythat says what attachments PostHog takes.Idempotency-Keywith another attachment answers422 idempotency_key_reused.How it works
send.attachments.Content.Simple.Attachments. A file with acontent_idgoes inline, any other as an attachment.helpers/raw-mime.tsare gone.attachments_omitted.attachments_omittedwith a warning, because it can't rebuild the send. The other rows of the page still rerun.Why the 512 KB cap: the content rides inside the queued workflow run, through the workflow queue and then the email queue, so it counts against the queue's message size. SES itself takes up to 40 MB.
Not in this PR: the SDK parameter (lanes Silthus/posthog#536 and Silthus/posthog#537).
How did you test this code?
Test seams:
EmailServiceagainst a faked SES client, for the SES attachments.state.sendto the delivered file.Test rationale: what each new test catches:
422 idempotency_key_reusedfor a key reused with another attachment.whysays what attachments PostHog takes.Content.Simple.Attachments: the invite as an attachment with its type, and the logo inline by its content id.state.sendto the email step removed.{ filename, contentType, size }, drops the content and carriesattachments_omitted.content_typeorcontent_idand takes 78, refuses a file named.exe, and refuses an unknown attachment key.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), case 01: a code send with cccopy@example.comandinvite.ics(text/calendar; method=REQUEST) answered202. Maildev got one email tojane@example.comwith the cc and the attachmentinvite.icsastext/calendar. Maildev receives through nodemailer, not SES, so the live run does not reach the SES message. TheEmailServicetest covers its attachments.Earlier live run on
9d7a26704b5, before the review fix and before native SES attachments: inline image and refusalsinvite.ics(text/calendar; method=REQUEST) and an inlinelogo.png(content_id: logo)202invite.icsastext/calendar; method=REQUEST; name=invite.ics, and the logo asContent-ID: <logo>setup.exe400attr: attachments__0__filename, "Mailbox providers block.exefiles. Send a link to the file instead."400attr: attachments, "Attachments can hold 512KB in total. Send a link to larger files."Cross-version (base
6a82b47b1b2, headb41402aca63, real Django body, cdp-api route, workers and result store in each tree):state.send.202; the invite arrives without its.ics.attachments_omittedand sends again without the files, or a worker from this head fails the step.Not checked: real SES. A sandbox SES account was not available to this lane, so delivery of SES attachments, open and click tracking on a send with attachments, and how mail clients render the invite, are unproven. The queue size of a full 512 KB send is unverified. 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)spike/email-attachments. The design pass replaced the spike's raw message with native SES attachments./tdd,/improving-drf-endpoints,/writing-tests,/writing-pr-descriptions,impact,merge-risk..agents/security.mdwas read before the attachment handling.keyandvalueturned the raw message's header map into one malformed header.Content.Simple.Attachments, over a raw MIME message, so every send keeps one SES message shape. Names that end in a dot or a space get the extension check too.content_typeandcontent_id, and a file named only.exe. Round 2 found nothing to fix. The rebase check in final read 3 found nothing.🤖 Generated with Claude Code