Skip to content

feat(tasks): download large living artifact files through presigned urls - #110967

Merged
trunk-io[bot] merged 3 commits into
masterfrom
posthog/task-living-artifact-download-redirect
Oct 2, 2026
Merged

trunk-io[bot] merged 3 commits into
masterfrom
posthog/task-living-artifact-download-redirect

Conversation

@puemos

@puemos puemos commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Problem

  • A person who downloads a large Slack file from a task's Artifacts tab holds a web worker for the whole download. The server reads the entire file into memory first.
  • Nothing caps the size, so a large video or archive can exhaust a worker's memory.
  • This is part of epic Epic: Artifacts on the web task page #109839.

Changes

  • Download: the Download button now asks for ?download=true. A stored version then redirects to a short-lived presigned URL. The file never passes through the app.
    • The link sets Content-Disposition: attachment and the file's content type, so agent-written HTML or SVG still saves instead of rendering.
    • Text versions (Slack canvases and messages) still stream, because their content lives in the database row.
  • Preview: images and video still load from the app origin, because the media-src policy allows video only from there. Redirecting the preview would need a CSP change.
    • A stored version over 25 MB returns 413 instead of loading into memory.
    • The Artifacts tab knows the size from the version record. It shows the existing no-preview state with the download button for such a file, so it never requests the 413.
  • Mechanical: regenerated OpenAPI types for the new download query parameter.

No new screen. A file over 25 MB shows the existing no-preview state, which already exists for CSV and text files.

How did you test this code?

  • Extended the backend tests: a download of a stored version redirects with the attachment disposition and reads no bytes, a text download streams, and an oversized preview returns 413 and reads no bytes.
  • Extended the frontend artifactPreviewKind table: a stored video over 25 MB gets no inline preview.
  • Generated a presigned link against the local object storage. It served the file with Content-Disposition: attachment and its content type.

Release status

  • No feature flag controls this change
  • This change is behind a feature flag and is not available to users
  • This change makes a previously flagged feature available to everyone

The Artifacts tab is behind today-rail-nav. The download parameter on the API is additive.

Automatic notifications

  • Publish to changelog?

Docs update

None.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: PostHog Desktop (Claude Code), Claude Opus 5.5

  • The user asked for presigned downloads, and agreed to keep previews on the app origin with a size limit, so no CSP change is needed.
  • Skills: /improving-drf-endpoints, /writing-tests, /writing-code-comments, /writing-pr-descriptions.

Created with PostHog Desktop

🤖 Generated with Claude Code

A download of a stored living artifact version (download=true) now redirects to a short-lived presigned URL, so the file no longer passes through a web worker. The link carries an attachment disposition and the file's content type. Text versions still stream.

A preview stays on the app origin, because the media-src policy allows video only from there. A stored version over 25 MB returns 413 instead of loading into memory, and the Artifacts tab shows the download state for it.

Generated-By: PostHog Desktop
Task-Id: e2367959-ab88-40ff-8761-5632645167aa
@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@puemos
puemos marked this pull request as ready for review October 2, 2026 16:01
@puemos puemos mentioned this pull request Oct 2, 2026
23 tasks done
@parameterai

parameterai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Risk: No findings

The increment replaces the fixed four-kind live-embed set in the Artifacts tab with a general one: any referenced object whose kind has a registered app page now renders that page in the same-origin embedded frame, and the full-page/link fallbacks follow the same rule. I traced the agent-controlled objectKind/objectId into the URL builder and confirmed the percent-encoding and ChoiceField validation on the write path prevent any origin escape, path/param injection, or parser crash, and that embedded pages enforce their own authorization with the viewer's session. No security issues introduced by these changes.

Sentinel reviewed 8230de9 · Review settings

@github-actions

github-actions Bot commented Oct 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) — 6 functions above the limit (max 33)

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.

Function Location Complexity Limit
ArtifactToolbar products/posthog_ai/frontend/scenes/TaskTracker/components/TaskRunArtifacts.tsx:725 33 10
ArtifactPreview products/posthog_ai/frontend/scenes/TaskTracker/components/TaskRunArtifacts.tsx:396 25 10
fileKind products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts:89 17 10
ArtifactsWorkspace products/posthog_ai/frontend/scenes/TaskTracker/components/TaskRunArtifacts.tsx:930 14 10
parseCsv products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts:156 14 10
editableArtifactKind products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts:386 13 10
✅ Duplication (Python) — clean

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.

✅ Duplication (TypeScript) — clean

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

⚠️ Comment density — 6% of added code lines are comments (12 of 201)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifactsLogic.ts 5 13
products/tasks/backend/logic/services/living_artifacts.py 4 44
products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts 2 7
products/tasks/backend/facade/api.py 1 45

This check does not block merging. It updates on every push and clears when the share drops.

⚠️ Bundle size — 🔺 +170 B (+0.0%)

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

Total: 69.79 MiB · 🔺 +170 B (+0.0%)

No file changed by more than 1000 B.

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.63 MiB · 22 files no change █████████░ 88.7% of 1.84 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
3.72 MiB · 661 files no change █████████░ 92.3% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.59 MiB · 2,409 files 🔺 +252 B (+0.0%) █████████░ 91.0% of 8.34 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.67 MiB · 3,392 files 🔺 +252 B (+0.0%) ███████░░░ 71.8% of 13.48 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.61 MiB · 2,417 files 🔺 +252 B (+0.0%) █████████░ 88.6% of 8.58 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.29 MiB · 3,244 files 🔺 +252 B (+0.0%) ███████░░░ 73.5% of 12.64 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
12.12 MiB · 4,130 files 🔺 +252 B (+0.0%) ████████░░ 77.1% of 15.72 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] 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/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.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

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.3 KiB src/index.tsx
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
854 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.3 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
100.5 KiB src/lib/api.ts
92.7 KiB src/products.tsx
69.4 KiB src/lib/lemon-ui/icons/icons.tsx
40.1 KiB src/lib/utils/eventUsageLogic.ts
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.0 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
315.5 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 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 #32479

✅ Toolbar bundle — eager 2.20 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.20 MiB · 19 files no change ████░░░░░░ 38.4% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 44 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
835.5 KiB dist/toolbar/toolbar-app-FSDWO46I.css
657.5 KiB dist/toolbar/chunk-chunk-BALLB66W.js
259.4 KiB dist/toolbar/chunk-chunk-7JWMBALG.js
138.2 KiB dist/toolbar/chunk-chunk-RXJG6CZC.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-YIYWW6XJ.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-MM7MZI2L.js
21.0 KiB dist/toolbar/chunk-chunk-EZFR5QGQ.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 — 🔺 +9.2 KiB (+0.0%)

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

Total: 960.36 MiB · 🔺 +9.2 KiB (+0.0%)

ℹ️ MCP UI apps size — 33 app(s), 17633.6 KB JS

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

App JS CSS
debug 597.9 KB 199.4 KB
action 454.1 KB 199.4 KB
action-list 564.2 KB 199.4 KB
cohort 453.1 KB 199.4 KB
cohort-list 563.2 KB 199.4 KB
email-template 452.9 KB 199.4 KB
error-details 469.6 KB 199.4 KB
error-issue 454.5 KB 199.4 KB
error-issue-list 564.8 KB 199.4 KB
experiment 561.3 KB 199.4 KB
experiment-list 564.9 KB 199.4 KB
experiment-results 566.3 KB 199.4 KB
feature-flag 566.8 KB 199.4 KB
feature-flag-list 570.5 KB 199.4 KB
feature-flag-testing 457.3 KB 199.4 KB
inline-scan 453.6 KB 199.4 KB
insight-actors 562.3 KB 199.4 KB
invite-email-preview 452.3 KB 199.4 KB
llm-costs 559.3 KB 199.4 KB
session-recording 455.3 KB 199.4 KB
survey 454.7 KB 199.4 KB
survey-global-stats 561.9 KB 199.4 KB
survey-list 564.9 KB 199.4 KB
survey-stats 561.9 KB 199.4 KB
trace-span 453.5 KB 199.4 KB
trace-span-list 564.1 KB 199.4 KB
vision-observation-list 563.3 KB 199.4 KB
workflow 453.4 KB 199.4 KB
workflow-list 563.5 KB 199.4 KB
loops-review 457.8 KB 199.4 KB
query-results 774.3 KB 199.4 KB
render-ui 858.4 KB 199.4 KB
visual-review-snapshots 457.9 KB 199.4 KB
⚠️ Playwright — 1 failed

🎭 Playwright report · View test results →

❌ 1 failed test:

  • create experiment via wizard, add metrics, and launch (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!

⚠️ Backend coverage — 92.0% of changed backend lines covered — 7 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ██████████████████░░ 92.0% (82 / 89)

File Patch Uncovered changed lines
products/tasks/backend/facade/api.py 78.3% 4429, 4432–4433, 4437, 4450
products/tasks/backend/presentation/views/api.py 84.6% 4573, 4578

🤖 Agents: add a test only if an uncovered line exposes a realistic regression that existing tests miss. Otherwise explain why no new test is needed under "How did you test this code?". Gap list: the patch-coverage artifact on this run (gh run download 486838320433093 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
demo ███████████░░░░░░░░░ 53.4% 1,445 / 2,707
data_tools ████████████░░░░░░░░ 61.2% 90 / 147
warehouse_sources_queue ██████████████░░░░░░ 68.2% 1,868 / 2,740
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
aeo ███████████████░░░░░ 76.3% 617 / 809
batch_exports ████████████████░░░░ 81.3% 21,587 / 26,561
apm █████████████████░░░ 84.1% 1,306 / 1,553
cdp ██████████████████░░ 88.3% 4,559 / 5,164
ml_inference ██████████████████░░ 88.8% 539 / 607
mcp_analytics ██████████████████░░ 89.2% 5,038 / 5,651
product_tours ██████████████████░░ 89.3% 1,340 / 1,500
dashboards ██████████████████░░ 89.6% 6,924 / 7,727
notebooks ██████████████████░░ 90.2% 15,514 / 17,207
signals ██████████████████░░ 90.4% 60,188 / 66,556
cohorts ██████████████████░░ 90.5% 8,534 / 9,434
data_warehouse ██████████████████░░ 90.6% 14,375 / 15,860
streamlit_apps ██████████████████░░ 90.8% 2,679 / 2,951
managed_warehouse ██████████████████░░ 91.0% 10,252 / 11,263
data_modeling ██████████████████░░ 91.2% 10,562 / 11,584
tasks ██████████████████░░ 91.3% 79,818 / 87,386
exports ██████████████████░░ 91.7% 9,684 / 10,566
business_knowledge ██████████████████░░ 92.0% 8,472 / 9,208
engineering_analytics ██████████████████░░ 92.2% 11,497 / 12,475
ai_training ██████████████████░░ 92.2% 356 / 386
today ██████████████████░░ 92.3% 999 / 1,082
webmcp ███████████████████░ 92.5% 248 / 268
early_access_features ███████████████████░ 92.6% 1,339 / 1,446
conversations ███████████████████░ 92.6% 29,225 / 31,559
visual_review ███████████████████░ 92.7% 10,000 / 10,785
managed_migrations ███████████████████░ 92.7% 1,581 / 1,705
stamphog ███████████████████░ 92.8% 8,109 / 8,742
canvas ███████████████████░ 92.9% 7,155 / 7,703
approvals ███████████████████░ 93.0% 3,974 / 4,271
mcp_registry ███████████████████░ 93.1% 1,670 / 1,794
notifications ███████████████████░ 93.2% 1,144 / 1,228
error_tracking ███████████████████░ 93.3% 16,389 / 17,573
surveys ███████████████████░ 93.4% 6,644 / 7,113
autoresearch ███████████████████░ 93.6% 8,955 / 9,572
slack_app ███████████████████░ 93.7% 14,611 / 15,600
context_layer ███████████████████░ 93.8% 3,415 / 3,639
web_analytics ███████████████████░ 93.9% 23,680 / 25,229
billing_alerts ███████████████████░ 94.1% 2,094 / 2,226
mcp_store ███████████████████░ 94.3% 8,959 / 9,501
wizard ███████████████████░ 94.7% 6,150 / 6,496
workflows ███████████████████░ 94.7% 15,227 / 16,072
reminders ███████████████████░ 94.8% 760 / 802
alerts ███████████████████░ 94.9% 10,037 / 10,575
ai_observability ███████████████████░ 94.9% 26,288 / 27,689
review_hog ███████████████████░ 95.0% 11,750 / 12,362
annotations ███████████████████░ 95.1% 817 / 859
endpoints ███████████████████░ 95.1% 9,234 / 9,706
customer_analytics ███████████████████░ 95.2% 26,116 / 27,428
legal_documents ███████████████████░ 95.2% 2,311 / 2,427
marketing_analytics ███████████████████░ 95.3% 19,450 / 20,413
posthog_ai ███████████████████░ 95.4% 2,530 / 2,653
actions ███████████████████░ 95.5% 756 / 792
logs ███████████████████░ 95.5% 15,456 / 16,188
experiments ███████████████████░ 95.5% 33,000 / 34,548
data_catalog ███████████████████░ 95.5% 4,401 / 4,606
tracing ███████████████████░ 95.6% 3,536 / 3,699
replay_vision ███████████████████░ 95.6% 29,389 / 30,727
growth ███████████████████░ 95.7% 11,381 / 11,888
skills ███████████████████░ 95.8% 6,972 / 7,274
messaging ███████████████████░ 95.9% 3,834 / 3,999
product_analytics ███████████████████░ 96.0% 28,521 / 29,696
revenue_analytics ███████████████████░ 96.4% 1,889 / 1,959
user_interviews ███████████████████░ 96.5% 2,870 / 2,974
feature_flags ███████████████████░ 96.6% 27,119 / 28,060
access_control ███████████████████░ 96.7% 7,739 / 8,007
warehouse_sources ███████████████████░ 97.3% 475,070 / 488,180
data_quality ████████████████████ 97.5% 7,701 / 7,895
links ████████████████████ 97.9% 234 / 239
metrics ████████████████████ 98.0% 4,265 / 4,351
security ████████████████████ 98.0% 1,304 / 1,330
analytics_platform ████████████████████ 98.3% 2,784 / 2,833
pulse ████████████████████ 98.5% 2,046 / 2,078
live_debugger ████████████████████ 99.2% 626 / 631
field_notes ████████████████████ 99.4% 172 / 173

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

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

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

commit 8230de9 · box box-4b7aa8ac8cf9 · ready in 748s (push → usable) · build log · rebuilds on every push, torn down on close

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested review from a team October 2, 2026 16:02
stamphog[bot]

This comment was marked as outdated.

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (16)
products/posthog_ai/frontend/AGENTS.md — auto-discovered
.cursor/rules/react-typescript.mdc — auto-discovered
.agents/skills/using-kea-disposables/SKILL.md — configured
.agents/skills/writing-ui-components/SKILL.md — configured
.agents/security.md — configured
services/mcp/AGENTS.md — auto-discovered
docs/published/handbook/engineering/type-system.md — configured
.agents/skills/implementing-mcp-tools/SKILL.md — configured
.agents/skills/adopting-generated-api-types/SKILL.md — configured
.agents/skills/placing-product-frontend-code/SKILL.md — configured
.agents/skills/writing-kea-logics/SKILL.md — configured
.agents/skills/writing-user-facing-copy/SKILL.md — configured
.agents/skills/implementing-mcp-ui-apps/SKILL.md — configured
docs/published/handbook/engineering/ai/implementing-mcp-tools.md — configured
.claude/commands/conventions.md — configured
.agents/skills/writing-code-comments/SKILL.md — configured
📝 Walkthrough

Walkthrough

Living artifact previews now have a 25 MiB limit. Stored versions can be downloaded through presigned attachment URLs, while oversized inline previews return 413. The frontend requests download-mode URLs and suppresses previews for oversized files. Tests cover download redirects, text responses, and oversized preview behavior.

Priority: ⬇️ Low

Merge Risk: 🟡 Moderate · up to 8230d

Missing size metadata can make the artifact preview fail with 413 instead of showing the no-preview state. Concurrent edits can also let an oversized artifact bypass the server’s preview limit and risk worker memory pressure. Resolve these delivery edge cases before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 8230d

Downloads retain task access checks and forced attachment behavior while avoiding application-side buffering. No new tenant-access bypass was identified. The preview-size protection remains incomplete under a preexisting concurrent-edit race, and storage-side deployment controls were not fully verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The new capability is a bearer download URL for the selected stored key, issued only after task access and team/task/run ownership checks. Possession of that URL permits storage access during its validity without repeating application authorization; this is not bucket-wide authority.

Trust Boundaries and Controls

  • observed — Caller-supplied artifact and version identifiers are resolved through the scoped run and artifact lookup. The shared resolver rejects storage locations outside the artifact's task prefix before the signer receives a key. The download branch retains the task:read requirement and shared task-access guard.

Resilience and Maintainability Implications

  • inferred — The inherited stale-size race can defeat the new worker-memory protection. Public edits require task:write and task access, so this is not an unauthenticated write path. Direct downloads avoid application buffering, but inline reads still consume the complete object after the size check.

Hardening Proposals

  • proposed — To make the preview limit a dependable resource boundary, enforce a byte-bounded read independently of recorded size and coordinate version allocation with immutable storage-object identity. These would strengthen an incomplete mitigation of preexisting behavior, not remedy an observed new tenant-access bypass.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is standalone and covers the problem, user-visible changes, testing, release status, documentation, and agent context. It omits the required explicit Test rationale and some agent defi…
✨ Finishing Touches 💡 1
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (2)
products/tasks/backend/logic/services/living_artifacts.py-424-424 (1)

424-424: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mock head_object in test_living_artifact_version_content.

The stored_file case omits size, so the endpoint calls object_storage.head_object before the patched read_bytes. The unpatched lookup can reach the configured object-storage client. Mock the metadata lookup to keep the test isolated.

Suggested fix
+    @patch("posthog.storage.object_storage.head_object")
     @patch("posthog.storage.object_storage.read_bytes")
-    def test_living_artifact_version_content(self, _name, version, expected_body, expected_type, mock_read_bytes):
+    def test_living_artifact_version_content(
+        self, _name, version, expected_body, expected_type, mock_read_bytes, mock_head_object
+    ):
+        mock_head_object.return_value = None
         mock_read_bytes.return_value = b"png bytes"

Source: Coding guidelines

products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts-72-72 (1)

72-72: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not preview stored versions with an unknown size.

When record.size is absent, artifact.size is undefined. The nullish fallback treats it as zero, so an oversized stored image or video can be selected for preview. The backend can then reject the request with 413, instead of showing the no-preview state. Treat unknown sizes as not previewable.

🐛 Suggested fix
-        if (!artifact.living.stored || (artifact.size ?? 0) > LIVING_PREVIEW_MAX_BYTES) {
+        if (!artifact.living.stored || artifact.size === undefined || artifact.size > LIVING_PREVIEW_MAX_BYTES) {
🧹 Nitpick comments (2)
products/tasks/backend/logic/services/living_artifacts.py (1)

389-389: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repository's frozen dataclass decorator.

Replace @dataclass(frozen=True) with @frozen and import frozen at module scope. As per coding guidelines: “Use @frozen from posthog.dataclasses.”

Source: Coding guidelines

products/tasks/backend/facade/api.py (1)

4420-4420: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Preserve the lazy facade import path when removing these local imports.

Both imports violate the backend module-level import rule. Do not move them directly into products/tasks/backend/facade/api.py: living_artifacts.py imports object_storage at module scope, and the presentation layer imports the facade module at module scope. Move the presigning entry point behind a module boundary that is not loaded during facade initialization, with its dependencies imported at module scope there.


ℹ️ Review info
⚙️ Run configuration

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

Review profile: QUIET

Plan: Enterprise

Run ID: c16dc900-346d-4b1f-b781-0330931dbb18

📥 Commits

Reviewing files that changed from the base of the PR and between 6d5d628 and 7ec7911.

⛔ Files ignored due to path filters (2)
  • products/tasks/frontend/generated/api.schemas.ts is excluded by !**/generated/**
  • products/tasks/frontend/generated/api.ts is excluded by !**/generated/**
📒 Files selected for processing (9)
  • products/posthog_ai/frontend/scenes/TaskTracker/components/TaskRunArtifacts.tsx
  • products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.test.ts
  • products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts
  • products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifactsLogic.ts
  • products/tasks/backend/facade/api.py
  • products/tasks/backend/logic/services/living_artifacts.py
  • products/tasks/backend/presentation/views/api.py
  • products/tasks/backend/tests/test_api.py
  • services/mcp/src/api/generated.ts

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

Comment on lines +429 to +447
def read_living_artifact_version(artifact: TaskArtifact, version: int) -> LivingArtifactVersionContent | None:
"""Return the content of one version, or None when the version is unknown or keeps no content.

A Slack file version keeps its bytes in object storage. A canvas or message version keeps its
text in the version record. A stored version above the preview limit raises
LivingArtifactVersionTooLarge. Storage read errors propagate to the caller.
"""
resolved = _resolve_living_version(artifact, version)
if resolved is None:
return None

if resolved.storage_path:
size = _stored_version_size(resolved)
if size is not None and size > LIVING_VERSION_PREVIEW_MAX_BYTES:
raise LivingArtifactVersionTooLarge()
payload = object_storage.read_bytes(resolved.storage_path, missing_ok=True)
if payload is None:
return None
return LivingArtifactVersionContent(name=artifact.name, content_type=content_type, content=payload)
return LivingArtifactVersionContent(name=artifact.name, content_type=resolved.content_type, content=payload)

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '388,465p' products/tasks/backend/logic/services/living_artifacts.py
rg -n 'def head_object|def read_bytes' posthog/storage/object_storage.py

Repository: PostHog/posthog

Length of output: 4464


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- object storage module slices ---'
sed -n '1,90p' posthog/storage/object_storage.py
sed -n '560,785p' posthog/storage/object_storage.py
printf '%s\n' '--- reader usages and endpoint context ---'
rg -n -C 5 'read_living_artifact_version|LivingArtifactVersionTooLarge|living_artifact_version_storage_path' products/tasks posthog | head -240
printf '%s\n' '--- relevant route/view names ---'
rg -n -C 4 'preview|living_artifact|artifact.*version|version.*artifact' products/tasks/backend products/tasks/frontend 2>/dev/null | head -260

Repository: PostHog/posthog

Length of output: 41511


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- object-storage read_bytes implementations ---'
sed -n '100,175p' posthog/storage/object_storage.py
sed -n '180,215p' posthog/storage/object_storage.py
sed -n '280,315p' posthog/storage/object_storage.py
printf '%s\n' '--- preview facade functions ---'
sed -n '4345,4465p' products/tasks/backend/facade/api.py
printf '%s\n' '--- callers and route registration ---'
rg -n -C 6 'get_living_artifact_version_content|get_living_artifact_version_url|living_artifact_version_content|living_artifact_version_url' products/tasks --glob '*.py'

Repository: PostHog/posthog

Length of output: 13071


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- exact facade callers and route bindings ---'
rg -n -C 5 'read_task_run_living_artifact_version' products/tasks --glob '*.py'
printf '%s\n' '--- preview endpoint test ---'
sed -n '8585,8705p' products/tasks/backend/tests/test_api.py

Repository: PostHog/posthog

Length of output: 9071


Enforce the preview limit when storage size is unknown.

When both the version record and head_object provide no usable size, the guard at living_artifacts.py:442-443 is skipped. The preview endpoint then calls read_bytes, whose S3 implementation uses Body.read() and loads the complete object. A stored object larger than 25 MiB can therefore consume unbounded worker memory. Reject unknown-size previews or read through a bounded storage operation before returning the content.

Suggested fix
         size = _stored_version_size(resolved)
-        if size is not None and size > LIVING_VERSION_PREVIEW_MAX_BYTES:
+        if size is None or size > LIVING_VERSION_PREVIEW_MAX_BYTES:
             raise LivingArtifactVersionTooLarge()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def read_living_artifact_version(artifact: TaskArtifact, version: int) -> LivingArtifactVersionContent | None:
"""Return the content of one version, or None when the version is unknown or keeps no content.
A Slack file version keeps its bytes in object storage. A canvas or message version keeps its
text in the version record. A stored version above the preview limit raises
LivingArtifactVersionTooLarge. Storage read errors propagate to the caller.
"""
resolved = _resolve_living_version(artifact, version)
if resolved is None:
return None
if resolved.storage_path:
size = _stored_version_size(resolved)
if size is not None and size > LIVING_VERSION_PREVIEW_MAX_BYTES:
raise LivingArtifactVersionTooLarge()
payload = object_storage.read_bytes(resolved.storage_path, missing_ok=True)
if payload is None:
return None
return LivingArtifactVersionContent(name=artifact.name, content_type=content_type, content=payload)
return LivingArtifactVersionContent(name=artifact.name, content_type=resolved.content_type, content=payload)
def read_living_artifact_version(artifact: TaskArtifact, version: int) -> LivingArtifactVersionContent | None:
"""Return the content of one version, or None when the version is unknown or keeps no content.
A Slack file version keeps its bytes in object storage. A canvas or message version keeps its
text in the version record. A stored version above the preview limit raises
LivingArtifactVersionTooLarge. Storage read errors propagate to the caller.
"""
resolved = _resolve_living_version(artifact, version)
if resolved is None:
return None
if resolved.storage_path:
size = _stored_version_size(resolved)
if size is None or size > LIVING_VERSION_PREVIEW_MAX_BYTES:
raise LivingArtifactVersionTooLarge()
payload = object_storage.read_bytes(resolved.storage_path, missing_ok=True)
if payload is None:
return None
return LivingArtifactVersionContent(name=artifact.name, content_type=resolved.content_type, content=payload)

@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
Scenes-App/Notebooks/Nodes/Customer Journey AllStepsCompleted smoke-test The test timed out while waiting for an element with the class '.react-flow__node' to become visible. Logs ↗︎
Scenes/Code review Default play-test The test failed because a logic component was not mounted when accessed, and there were unhandled network requests intercepted by the mock service... Logs ↗︎
Scenes-App/SidePanels SidePanelNotebooks smoke-test The test timed out while waiting for loading indicators or spinners to disappear. Logs ↗︎
compareTopLevelSections() reports a modifiers change when the current query overrides the team default A TypeError occurred because the code attempted to access the 'add' property of an undefined object. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

The presign facade returns a LivingArtifactVersionDownload contract, and the logic layer exposes the resolved version as a frozen LivingVersionLocation, instead of tuples a caller reads by position. The view no longer reuses one variable for the redirect and the streamed response, which mypy rejected.

Generated-By: PostHog Desktop
Task-Id: e2367959-ab88-40ff-8761-5632645167aa
@stamphog
stamphog Bot dismissed their stale review October 2, 2026 16:46

New commits were pushed — dismissing the stamphog approval from an earlier head. Re-add the trigger label to request a fresh review.

stamphog[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
products/tasks/backend/facade/api.py-4441-4441 (1)

4441-4441: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle storage-client initialization failures as "unavailable".

Signing failures already return None, but object_storage_client() can raise ValueError while creating the client for an invalid endpoint. That call occurs before the presigning handler, so the endpoint can return a server error instead of "unavailable".

🐛 Suggested fix
-    url = object_storage.get_presigned_url(
-        resolved.storage_path,
-        content_type=resolved.content_type or None,
-        # Agent-written HTML or SVG must not render as a page, so the browser always saves it.
-        content_disposition=content_disposition_header(
-            as_attachment=True, filename=PurePosixPath(artifact.name).name or "artifact"
-        )
-        or "attachment",
-    )
+    try:
+        url = object_storage.get_presigned_url(
+            resolved.storage_path,
+            content_type=resolved.content_type or None,
+            # Agent-written HTML or SVG must not render as a page, so the browser always saves it.
+            content_disposition=content_disposition_header(
+                as_attachment=True, filename=PurePosixPath(artifact.name).name or "artifact"
+            )
+            or "attachment",
+        )
+    except ValueError:
+        return contracts.LivingArtifactVersionDownload(url=None, error="unavailable")

ℹ️ Review info
⚙️ Run configuration

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

Review profile: QUIET

Plan: Enterprise

Run ID: efdeba18-8b3a-4158-b75d-103b35a148a9

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec7911 and 4931dfa.

📒 Files selected for processing (4)
  • products/tasks/backend/facade/api.py
  • products/tasks/backend/facade/contracts.py
  • products/tasks/backend/logic/services/living_artifacts.py
  • products/tasks/backend/presentation/views/api.py

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


from products.tasks.backend.logic.services.living_artifacts import ( # noqa: PLC0415 — keep storage deps off the api import path
get_task_artifact_for_run,
resolve_living_artifact_version,

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,100p' products/tasks/backend/facade/api.py
sed -n '4370,4455p' products/tasks/backend/facade/api.py
sed -n '1,85p' products/tasks/backend/logic/services/living_artifacts.py

Repository: PostHog/posthog

Length of output: 10234


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- api.py imports and relevant references ---'
rg -n -C 3 'living_artifacts|object_storage|read_task_run_living_artifact_version|presign_task_run_living_artifact_version_download' products/tasks/backend/facade/api.py

printf '%s\n' '--- living_artifacts.py imports and relevant definitions ---'
sed -n '1,120p' products/tasks/backend/logic/services/living_artifacts.py
rg -n -C 3 'def (get_task_artifact_for_run|resolve_living_artifact_version|read_living_artifact_version)|from products.tasks.backend.facade|import products.tasks.backend.facade|from posthog.storage|object_storage' products/tasks/backend/logic/services/living_artifacts.py

printf '%s\n' '--- storage module location and imports ---'
fd -t f 'object_storage*' posthog products
rg -n -C 4 'from posthog.storage import object_storage|import object_storage|class .*Storage|def get_presigned_url|object_storage[[:space:]]*=' posthog/storage products/tasks/backend

Repository: PostHog/posthog

Length of output: 41990


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- storage package files ---'
fd -t f . posthog/storage | sort

printf '%s\n' '--- object_storage.py header and client initialization ---'
sed -n '1,180p' posthog/storage/object_storage.py
rg -n -C 3 '^(object_storage|class |def |[A-Z_]+ =)|boto|S3|Storage' posthog/storage/object_storage.py | head -160

printf '%s\n' '--- storage package initializer ---'
if [ -f posthog/storage/__init__.py ]; then sed -n '1,160p' posthog/storage/__init__.py; fi

printf '%s\n' '--- facade contracts imports and header ---'
sed -n '1,100p' products/tasks/backend/facade/contracts.py
rg -n 'products\.tasks\.backend\.facade\.api|from .*facade import api|import .*facade\.api' products/tasks/backend/logic/services/living_artifacts.py products/tasks/backend/facade/contracts.py

printf '%s\n' '--- direct api imports from living_artifacts service ---'
rg -n 'products\.tasks\.backend\.facade\.api|facade import api|from products\.tasks\.backend\.facade' products/tasks/backend/logic/services/living_artifacts.py

Repository: PostHog/posthog

Length of output: 16642


Move the resolver import to module scope.

The local import violates the backend import rule. living_artifacts.py imports object_storage, but this does not eagerly initialize a storage client: object_storage.py starts with UnavailableStorage, and imports boto3 only when object_storage_client() is called. Do not split the module solely to avoid eager storage initialization.

content: bytes


@dataclass(frozen=True, kw_only=True)

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the repository dataclass decorator.

Replace @dataclass(frozen=True, kw_only=True) with @frozen and import it from posthog.dataclasses. As per coding guidelines, “Use @frozen from posthog.dataclasses.”

Proposed decorator change
-@dataclass(frozen=True, kw_only=True)
+@frozen

Source: Coding guidelines

…rect

Generated-By: PostHog Desktop
Task-Id: e0e344ed-ed9b-462a-8e26-9d05263ffd60
@stamphog
stamphog Bot dismissed their stale review October 2, 2026 18:54

New commits were pushed — dismissing the stamphog approval from an earlier head. Re-add the trigger label to request a fresh review.

@puemos puemos added the stamphog Request AI approval (no full review) label Oct 2, 2026 — with PostHog

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved.

The presigned download path keeps the run-visibility check, artifact scoping and task-prefix check, and the author has STRONG familiarity with every file changed. The open CodeRabbit comments are minor (test isolation, an edge-case frontend fallback) and none is a showstopper.

  • Author wrote 100% of the modified lines and has 47 merged PRs in these paths (familiarity STRONG).
  • Minor: test_living_artifact_version_content may call head_object unmocked when a stored file has no size (CodeRabbit nit).
  • Minor: the frontend treats an unknown size as previewable, so the backend can return 413 in that edge case. The backend still enforces the limit.
  • Minor: object_storage client init errors are not mapped to 'unavailable' in the presign path.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 200L, 7F substantive, 298L/12F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (298L, 12F, two-areas, feat)
stamphog 2.3.1 .stamphog/policy.yml @ 8230de9 · reviewed head 8230de9

@coderabbitai coderabbitai 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.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts-76-76 (1)

76-76: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat an unknown stored artifact size as non-previewable.

If a stored living artifact omits size, (artifact.size ?? 0) classifies it as previewable. The preview then uses the app download path, although the backend can reject an oversized stored version. Preserve the size invariant for stored versions, or return 'none' when size is missing.


ℹ️ Review info
⚙️ Run configuration

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

Review profile: QUIET

Plan: Enterprise

Run ID: fd2d8267-9027-487c-acba-4bfadfa09284

📥 Commits

Reviewing files that changed from the base of the PR and between 4931dfa and 8230de9.

⛔ Files ignored due to path filters (2)
  • products/tasks/frontend/generated/api.schemas.ts is excluded by !**/generated/**
  • products/tasks/frontend/generated/api.ts is excluded by !**/generated/**
📒 Files selected for processing (3)
  • products/posthog_ai/frontend/scenes/TaskTracker/components/TaskRunArtifacts.tsx
  • products/posthog_ai/frontend/scenes/TaskTracker/taskRunArtifacts.ts
  • services/mcp/src/api/generated.ts

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

puemos commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

/trunk merge

1 similar comment

puemos commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

/trunk merge

@trunk-io
trunk-io Bot merged commit 21aea99 into master Oct 2, 2026
450 of 452 checks passed
@trunk-io
trunk-io Bot deleted the posthog/task-living-artifact-download-redirect branch October 2, 2026 22:13
@deployment-status-posthog

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

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-10-02 22:33 UTC Run
prod-us ✅ Deployed 2026-10-02 22:42 UTC Run
prod-eu ✅ Deployed 2026-10-02 22:44 UTC Run

This branch was successfully deployed

1 active deployment
preview-pr-110967 — 8230de9d Deployed Oct 2, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature/desktop Feature Tag: Desktop stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant