Skip to content

feat(agents): prod-readiness wiring — Modal sandbox, Redis-only bus, approvals, gateway#61452

Merged
dmarticus merged 15 commits into
assfrom
agents-prod-readiness
Jun 4, 2026
Merged

feat(agents): prod-readiness wiring — Modal sandbox, Redis-only bus, approvals, gateway#61452
dmarticus merged 15 commits into
assfrom
agents-prod-readiness

Conversation

@benjackwhite

Copy link
Copy Markdown
Contributor

Problem

The agent platform services were deployable in dev but several pieces blocked actually running real workloads end-to-end:

  • Custom-tool sandbox defaulted to in-process, so author JS ran in the runner pod with no isolation; Modal was a stub that threw on acquireForSession.
  • Runner + ingress fell back to an in-memory SessionEventBus when REDIS_URL was unset, so SSE on ingress pod A silently missed events from runner pod B.
  • Approvals: PgApprovalStore was fully implemented but never constructed in either prod entrypoint, so every requires_approval tool was silently ungated and /approvals/* returned 503.
  • Dead egressProxyUrl plumbing on the sandbox interface that no caller actually wired (smokescreen owns egress now).
  • @posthog/web-fetch shipped a "permitting all hosts" warning since the allowlist was never threaded into context — infra-level smokescreen handles this now.

Companion chart + terraform PRs:

Changes

Modal sandbox (B1)

  • New ModalSandboxPool against the official modal npm SDK (one Modal Sandbox per AgentSession, node:24-alpine base image, tools + dispatch.js laid out via sandbox.filesystem.writeText, per-invoke sandbox.exec(['node', '/sandbox/dispatch.js', ...])). The dispatcher script is inlined as a string constant — mirrors the existing services/agent-sandbox-host wire format so the file-based contract is portable.
  • selectSandboxPool no longer accepts in-process; it throws at boot when SANDBOX_BACKEND isn't modal or docker. InProcessSandboxPool the class stays, but tests construct it directly — it's just not reachable from prod entry points anymore.
  • Inputs come from MODAL_TOKEN_ID + MODAL_TOKEN_SECRET (read directly by the Modal SDK) wired in the chart from agent-platform-shared-secrets.

Redis-only SessionEventBus (B8)

  • Runner + ingress no longer fall back to MemorySessionEventBus when REDIS_URL is unset — they throw at boot with a clear error pointing at the chart wiring.
  • PlatformConfigSchema.redisUrl keeps an optional isDev() default of redis://localhost:6379 so test fixtures / local dev still work; prod (NODE_ENV=production) gets no default and the entrypoint check fires.
  • Valkey is provisioned in the cloud-infra PR; the chart's valkey: agent-platform: map wires REDIS_URL from REDIS_WRITER_URL automatically.

Approvals (B4)

  • Wire new PgApprovalStore(agentDb) into both the runner Worker and buildJanitorApp (plus the sweep config) — matches the test harness exactly. requires_approval tools now actually intercept; /approvals/* HTTP surface no longer 503s.
  • Console UI for approvals is still hardcoded to 0; deciding an approval happens via curl or MCP for now. Tracked in the plan as H3.

Egress plumbing cleanup (B2)

  • Strip egressProxyUrl from AcquireOpts + InProcessSandbox. Strip EgressPolicy + egress from SandboxLimits. Update the secret-broker comment.

web-fetch warning (B3)

  • Drop the allowlist not yet enforced; permitting all warning + the dead spec-field hook. Description now says outbound host filtering lives at the infrastructure layer.

How did you test this code?

I'm an agent (Claude Opus 4.7 via Claude Code). Manual testing is what you'd do in the cluster — I ran:

  • pnpm --filter @posthog/agent-shared --filter @posthog/agent-runner --filter @posthog/agent-ingress --filter @posthog/agent-janitor exec tsc --noEmit — clean across every iteration.
  • pnpm --filter @posthog/agent-{shared,runner,ingress,janitor,tools} test --run — all unit suites pass (294 tests across the four packages).
  • AGENT_SKIP_REAL_INFERENCE=1 pnpm --filter @posthog/agent-tests test --run — full e2e harness, 158 passed / 7 skipped (the 7 skipped are real-inference-only by env flag).
  • pnpm --filter @posthog/agent-shared --filter @posthog/agent-runner --filter @posthog/agent-ingress --filter @posthog/agent-janitor --filter @posthog/agent-tools lint — no new warnings or errors.
  • pnpm run build in services/agents — esbuild bundles cleanly; runner bundle picks up the Modal SDK (grep -c "ModalClient\|sandboxes.create" dist/runner.mjs → 19 references).

Not verified by me:

  • The Modal sandbox path against a live Modal account (I have no runtime access from here). The contract is exercised against the SDK types but the actual sandboxes.createfilesystem.writeTextexecwait flow needs a real session to confirm.
  • The chart side — needs an Argo sync to dev once the charts PR lands.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

docs/agent-platform/docs/deploy-runbook.md will need the new env vars (SANDBOX_BACKEND=modal, MODAL_TOKEN_ID/MODAL_TOKEN_SECRET, removal of the "Redis optional" note). Happy to follow up in this PR or a separate one.

🤖 Agent context

Authored by Claude Opus 4.7 (1M context) via Claude Code, working from the running plan at ~/.claude/plans/agent-platform-prod-readiness.md. Ben drove the scope decisions:

  • Modal over Docker for prod isolation. Stub gets replaced with a real impl against the official modal npm SDK rather than a "TODO" placeholder.
  • Drop in-process from the prod selector entirely (fail-fast at boot) instead of warning-then-using.
  • Drop MemorySessionEventBus from prod boot, require REDIS_URL. New Valkey serverless in the agent-platform terraform module backs it.
  • Always-on ai-gateway via the cluster-internal service. The runner's PgTeamApiKeyResolver reads posthog_team.api_token so no upstream provider keys are needed in the agent-platform secrets bag.
  • Wire approvals despite the console UI being stubbed — backend is 20 lines and the runner intercept needs it.

Things explicitly skipped per Ben's scoping: prod-us / prod-eu Argo blocks, prod terraform, public ingress for agent-console / agent-ingress (Tailscale dev is fine for now). Tracked as the "Out of scope" section in the plan.

The dispatcher script is inlined as a string constant in sandbox-modal.ts rather than reused from services/agent-sandbox-host/ to keep agent-shared self-contained (no runtime file resolution, no new package to publish). If Docker continues to be useful for local dev, the two should eventually share a single source — flagged as a follow-up but not blocking.

…approvals, gateway

Several changes across the agent platform services to make the dev deploy
actually work end-to-end:

- Sandbox: implement Modal-backed `ModalSandboxPool` (per-session sandbox via
  the official `modal` npm SDK; tools + dispatch.js written to /sandbox +
  /workdir; per-invoke exec). Drop `in-process` from the selector and
  fail-fast at boot if `SANDBOX_BACKEND` isn't `modal` or `docker`. The
  `InProcessSandboxPool` class stays for the test harness; just no longer
  reachable from prod entry points.
- Strip dead `egressProxyUrl` / `EgressPolicy` plumbing — egress filtering
  lives at the sandbox boundary + cluster-level smokescreen now.
- Bus: drop `MemorySessionEventBus` from the runner + ingress boot paths.
  Both throw at boot if `REDIS_URL` is unset; harness still wires the
  in-memory bus directly.
- Approvals: wire `PgApprovalStore` into both the runner Worker and the
  janitor (server + sweep), matching the test harness.
- web-fetch: drop the unenforced-allowlist warning + dead spec field.
  Outbound host filtering lives at smokescreen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 81.1 MB

ℹ️ View Unchanged
Filename Size
frontend/dist-report/decompression-worker/src/scenes/session-recordings/player/snapshot-processing/decompressionWorker 2.85 kB
frontend/dist-report/exporter/_chunks/chunk 8.46 MB
frontend/dist-report/exporter/_parent/products/actions/frontend/pages/Action 25 kB
frontend/dist-report/exporter/_parent/products/actions/frontend/pages/Actions 1.33 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/AIObservabilityScene 119 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/AIObservabilitySessionScene 19.9 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/AIObservabilityTraceScene 132 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/AIObservabilityUsers 872 B
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/clusters/AIObservabilityClusterScene 21.7 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/clusters/AIObservabilityClustersScene 55.1 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/datasets/AIObservabilityDatasetScene 21 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/datasets/AIObservabilityDatasetsScene 3.64 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluation 59.9 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluationsScene 28.2 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/evaluations/EvaluationTemplates 915 B
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/LLMASessionFeedbackDisplay 5.19 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/playground/AIObservabilityPlaygroundScene 37.8 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/prompts/LLMPromptScene 29.2 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/prompts/LLMPromptsScene 4.83 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/skills/LLMSkillScene 929 B
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/skills/LLMSkillsScene 946 B
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/tags/AIObservabilityTag 27.4 kB
frontend/dist-report/exporter/_parent/products/ai_observability/frontend/tags/AIObservabilityTagsScene 7.31 kB
frontend/dist-report/exporter/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeScene 19 kB
frontend/dist-report/exporter/_parent/products/conversations/frontend/components/Assignee/CyclotronJobInputAssignee 1.67 kB
frontend/dist-report/exporter/_parent/products/conversations/frontend/components/SlaBusinessHours/CyclotronJobInputBusinessHours 3.06 kB
frontend/dist-report/exporter/_parent/products/conversations/frontend/components/TicketTags/CyclotronJobInputTicketTags 1.06 kB
frontend/dist-report/exporter/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene 1.82 kB
frontend/dist-report/exporter/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene 34.4 kB
frontend/dist-report/exporter/_parent/products/conversations/frontend/scenes/tickets/SupportTicketsScene 1.07 kB
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene 63 kB
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene 2.65 kB
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene 2.18 kB
frontend/dist-report/exporter/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene 7.86 kB
frontend/dist-report/exporter/_parent/products/data_warehouse/DataWarehouseScene 46.8 kB
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/NewSourceScene/NewSourceScene 1.15 kB
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/SchemaScene/SchemaScene 26.6 kB
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/SourceScene/SourceScene 1.1 kB
frontend/dist-report/exporter/_parent/products/data_warehouse/frontend/scenes/SourcesScene/SourcesScene 6.31 kB
frontend/dist-report/exporter/_parent/products/early_access_features/frontend/EarlyAccessFeature 1.02 kB
frontend/dist-report/exporter/_parent/products/early_access_features/frontend/EarlyAccessFeatures 3.24 kB
frontend/dist-report/exporter/_parent/products/endpoints/frontend/EndpointScene 40.6 kB
frontend/dist-report/exporter/_parent/products/endpoints/frontend/EndpointsScene 24.5 kB
frontend/dist-report/exporter/_parent/products/error_tracking/frontend/scenes/ErrorTrackingFingerprintsScene/ErrorTrackingIssueFingerprintsScene 7.44 kB
frontend/dist-report/exporter/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene 104 kB
frontend/dist-report/exporter/_parent/products/error_tracking/frontend/scenes/ErrorTrackingScene/ErrorTrackingScene 27.1 kB
frontend/dist-report/exporter/_parent/products/feature_flags/frontend/FeatureFlagTemplatesScene 7.38 kB
frontend/dist-report/exporter/_parent/products/games/368Hedgehogs/368Hedgehogs 5.61 kB
frontend/dist-report/exporter/_parent/products/games/FlappyHog/FlappyHog 6.12 kB
frontend/dist-report/exporter/_parent/products/legal_documents/frontend/scenes/LegalDocumentNewScene 60.5 kB
frontend/dist-report/exporter/_parent/products/legal_documents/frontend/scenes/LegalDocumentsScene 5.31 kB
frontend/dist-report/exporter/_parent/products/links/frontend/LinkScene 25.2 kB
frontend/dist-report/exporter/_parent/products/links/frontend/LinksScene 4.55 kB
frontend/dist-report/exporter/_parent/products/live_debugger/frontend/LiveDebugger 19.5 kB
frontend/dist-report/exporter/_parent/products/logs/frontend/LogsScene 17.9 kB
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsAlertDetailScene/LogsAlertDetailScene 17.2 kB
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsAlertNotificationDetailScene/LogsAlertNotificationDetailScene 8.49 kB
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsSamplingDetailScene/LogsSamplingDetailScene 5.3 kB
frontend/dist-report/exporter/_parent/products/logs/frontend/scenes/LogsSamplingNewScene/LogsSamplingNewScene 2.25 kB
frontend/dist-report/exporter/_parent/products/managed_migrations/frontend/ManagedMigration 14.9 kB
frontend/dist-report/exporter/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene 40.6 kB
frontend/dist-report/exporter/_parent/products/mcp_analytics/frontend/MCPAnalyticsToolDetail 18.5 kB
frontend/dist-report/exporter/_parent/products/metrics/frontend/MetricsScene 16.2 kB
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/stickiness/StickinessBarChart/StickinessBarChart 3.31 kB
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/stickiness/StickinessLineChart/StickinessLineChart 3.14 kB
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsBarChart/TrendsBarChart 8.48 kB
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsLifecycleChart/TrendsLifecycleChart 4.41 kB
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsLineChart/TrendsLineChart 4.6 kB
frontend/dist-report/exporter/_parent/products/product_analytics/frontend/insights/trends/TrendsPieChart/TrendsPieChart 4.35 kB
frontend/dist-report/exporter/_parent/products/replay_vision/frontend/observations/ReplayObservation 12.7 kB
frontend/dist-report/exporter/_parent/products/replay_vision/frontend/replay_scanners/ReplayScanner 36.8 kB
frontend/dist-report/exporter/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene 12.2 kB
frontend/dist-report/exporter/_parent/products/replay_vision/frontend/replay_scanners/ScannerTemplatesScene 4.59 kB
frontend/dist-report/exporter/_parent/products/revenue_analytics/frontend/RevenueAnalyticsScene 26.5 kB
frontend/dist-report/exporter/_parent/products/session_summaries/frontend/SessionGroupSummariesTable 5.05 kB
frontend/dist-report/exporter/_parent/products/session_summaries/frontend/SessionGroupSummaryScene 19.2 kB
frontend/dist-report/exporter/_parent/products/tasks/frontend/SlackTaskContextScene 8.88 kB
frontend/dist-report/exporter/_parent/products/tasks/frontend/TaskDetailScene 23.8 kB
frontend/dist-report/exporter/_parent/products/tasks/frontend/TaskTracker 14.6 kB
frontend/dist-report/exporter/_parent/products/tracing/frontend/TracingScene 67.2 kB
frontend/dist-report/exporter/_parent/products/user_interviews/frontend/UserInterview 9.37 kB
frontend/dist-report/exporter/_parent/products/user_interviews/frontend/UserInterviewResponse 7.79 kB
frontend/dist-report/exporter/_parent/products/user_interviews/frontend/UserInterviews 6.08 kB
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewIndexScene 2.56 kB
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewRunScene 44.8 kB
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewRunsScene 7.32 kB
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewSettingsScene 11.1 kB
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotHistoryScene 13.9 kB
frontend/dist-report/exporter/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotOverviewScene 19.6 kB
frontend/dist-report/exporter/_parent/products/workflows/frontend/TemplateLibrary/MessageTemplate 16.6 kB
frontend/dist-report/exporter/_parent/products/workflows/frontend/Workflows/WorkflowScene 111 kB
frontend/dist-report/exporter/_parent/products/workflows/frontend/WorkflowsScene 60.2 kB
frontend/dist-report/exporter/src/exporter/exporter 19.7 kB
frontend/dist-report/exporter/src/exporter/scenes/ExporterDashboardScene 2.02 kB
frontend/dist-report/exporter/src/exporter/scenes/ExporterHeatmapScene 19.9 kB
frontend/dist-report/exporter/src/exporter/scenes/ExporterInsightScene 3.02 kB
frontend/dist-report/exporter/src/exporter/scenes/ExporterInterviewScene 310 kB
frontend/dist-report/exporter/src/exporter/scenes/ExporterNotebookScene 2.71 MB
frontend/dist-report/exporter/src/exporter/scenes/ExporterRecordingScene 1.13 kB
frontend/dist-report/exporter/src/exporterSharedChunkAnchors 1.19 kB
frontend/dist-report/exporter/src/lib/components/Cards/TextCard/TextCardMarkdownEditor 11.3 kB
frontend/dist-report/exporter/src/lib/components/MonacoDiffEditor 471 B
frontend/dist-report/exporter/src/lib/lemon-ui/LemonMarkdown/MermaidDiagram 2.25 kB
frontend/dist-report/exporter/src/lib/lemon-ui/LemonTextArea/LemonTextAreaMarkdown 842 B
frontend/dist-report/exporter/src/lib/lemon-ui/Link/Link 359 B
frontend/dist-report/exporter/src/lib/monaco/CodeEditorInline 832 B
frontend/dist-report/exporter/src/lib/monaco/vimMode 211 kB
frontend/dist-report/exporter/src/lib/ui/Button/ButtonPrimitives 422 B
frontend/dist-report/exporter/src/queries/nodes/WebVitals/WebVitals 7.52 kB
frontend/dist-report/exporter/src/queries/nodes/WebVitals/WebVitalsPathBreakdown 4.09 kB
frontend/dist-report/exporter/src/queries/schema 860 kB
frontend/dist-report/exporter/src/scenes/approvals/changeRequestsLogic 884 B
frontend/dist-report/exporter/src/scenes/authentication/passkeyLogic 824 B
frontend/dist-report/exporter/src/scenes/data-pipelines/event-filtering/EventFilterScene 22.2 kB
frontend/dist-report/exporter/src/scenes/data-pipelines/TransformationsScene 6.58 kB
frontend/dist-report/exporter/src/scenes/insights/views/BoxPlot/BoxPlot 5.39 kB
frontend/dist-report/exporter/src/scenes/insights/views/CalendarHeatMap/CalendarHeatMap 8.84 kB
frontend/dist-report/exporter/src/scenes/insights/views/RegionMap/RegionMap 29.8 kB
frontend/dist-report/exporter/src/scenes/insights/views/WorldMap/WorldMap 1.04 MB
frontend/dist-report/exporter/src/scenes/models/ModelsScene 19 kB
frontend/dist-report/exporter/src/scenes/models/NodeDetailScene 17 kB
frontend/dist-report/monaco-editor-worker/src/lib/monaco/workers/monacoEditorWorker 288 kB
frontend/dist-report/monaco-json-worker/src/lib/monaco/workers/monacoJsonWorker 419 kB
frontend/dist-report/monaco-typescript-worker/src/lib/monaco/workers/monacoTsWorker 7.02 MB
frontend/dist-report/posthog-app/_chunks/chunk 8.67 MB
frontend/dist-report/posthog-app/_parent/products/actions/frontend/pages/Action 25.1 kB
frontend/dist-report/posthog-app/_parent/products/actions/frontend/pages/Actions 1.4 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/AIObservabilityScene 120 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/AIObservabilitySessionScene 19.9 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/AIObservabilityTraceScene 132 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/AIObservabilityUsers 906 B
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/clusters/AIObservabilityClusterScene 21.7 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/clusters/AIObservabilityClustersScene 55.1 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/datasets/AIObservabilityDatasetScene 21 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/datasets/AIObservabilityDatasetsScene 3.67 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluation 59.9 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluationsScene 28.2 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/evaluations/EvaluationTemplates 949 B
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/LLMASessionFeedbackDisplay 5.23 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/playground/AIObservabilityPlaygroundScene 37.8 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/prompts/LLMPromptScene 29.2 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/prompts/LLMPromptsScene 4.86 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/skills/LLMSkillScene 963 B
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/skills/LLMSkillsScene 980 B
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTag 27.4 kB
frontend/dist-report/posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagsScene 7.34 kB
frontend/dist-report/posthog-app/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeScene 19 kB
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/components/Assignee/CyclotronJobInputAssignee 1.71 kB
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/components/SlaBusinessHours/CyclotronJobInputBusinessHours 3.09 kB
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/components/TicketTags/CyclotronJobInputTicketTags 1.09 kB
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene 1.85 kB
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene 26.6 kB
frontend/dist-report/posthog-app/_parent/products/conversations/frontend/scenes/tickets/SupportTicketsScene 1.11 kB
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene 61.7 kB
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene 2.68 kB
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene 2.22 kB
frontend/dist-report/posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene 7.9 kB
frontend/dist-report/posthog-app/_parent/products/data_warehouse/DataWarehouseScene 1.81 kB
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/NewSourceScene/NewSourceScene 1.25 kB
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/SchemaScene/SchemaScene 26.7 kB
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/SourceScene/SourceScene 1.17 kB
frontend/dist-report/posthog-app/_parent/products/data_warehouse/frontend/scenes/SourcesScene/SourcesScene 6.34 kB
frontend/dist-report/posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeature 1.2 kB
frontend/dist-report/posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeatures 3.28 kB
frontend/dist-report/posthog-app/_parent/products/endpoints/frontend/EndpointScene 40.7 kB
frontend/dist-report/posthog-app/_parent/products/endpoints/frontend/EndpointsScene 22.4 kB
frontend/dist-report/posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingFingerprintsScene/ErrorTrackingIssueFingerprintsScene 7.51 kB
frontend/dist-report/posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene 103 kB
frontend/dist-report/posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingScene/ErrorTrackingScene 27.3 kB
frontend/dist-report/posthog-app/_parent/products/feature_flags/frontend/FeatureFlagTemplatesScene 7.42 kB
frontend/dist-report/posthog-app/_parent/products/games/368Hedgehogs/368Hedgehogs 5.65 kB
frontend/dist-report/posthog-app/_parent/products/games/FlappyHog/FlappyHog 6.16 kB
frontend/dist-report/posthog-app/_parent/products/legal_documents/frontend/scenes/LegalDocumentNewScene 60.5 kB
frontend/dist-report/posthog-app/_parent/products/legal_documents/frontend/scenes/LegalDocumentsScene 5.35 kB
frontend/dist-report/posthog-app/_parent/products/links/frontend/LinkScene 25.2 kB
frontend/dist-report/posthog-app/_parent/products/links/frontend/LinksScene 4.58 kB
frontend/dist-report/posthog-app/_parent/products/live_debugger/frontend/LiveDebugger 19.5 kB
frontend/dist-report/posthog-app/_parent/products/logs/frontend/LogsScene 17.9 kB
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsAlertDetailScene/LogsAlertDetailScene 17.3 kB
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsAlertNotificationDetailScene/LogsAlertNotificationDetailScene 8.53 kB
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsSamplingDetailScene/LogsSamplingDetailScene 5.34 kB
frontend/dist-report/posthog-app/_parent/products/logs/frontend/scenes/LogsSamplingNewScene/LogsSamplingNewScene 2.29 kB
frontend/dist-report/posthog-app/_parent/products/managed_migrations/frontend/ManagedMigration 14.9 kB
frontend/dist-report/posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene 40.6 kB
frontend/dist-report/posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsToolDetail 18.6 kB
frontend/dist-report/posthog-app/_parent/products/metrics/frontend/MetricsScene 16.2 kB
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/stickiness/StickinessBarChart/StickinessBarChart 3.35 kB
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/stickiness/StickinessLineChart/StickinessLineChart 3.18 kB
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsBarChart/TrendsBarChart 8.51 kB
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsLifecycleChart/TrendsLifecycleChart 4.45 kB
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsLineChart/TrendsLineChart 4.64 kB
frontend/dist-report/posthog-app/_parent/products/product_analytics/frontend/insights/trends/TrendsPieChart/TrendsPieChart 4.38 kB
frontend/dist-report/posthog-app/_parent/products/replay_vision/frontend/observations/ReplayObservation 12.7 kB
frontend/dist-report/posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScanner 36.8 kB
frontend/dist-report/posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene 12.2 kB
frontend/dist-report/posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ScannerTemplatesScene 4.63 kB
frontend/dist-report/posthog-app/_parent/products/revenue_analytics/frontend/RevenueAnalyticsScene 26.7 kB
frontend/dist-report/posthog-app/_parent/products/session_summaries/frontend/SessionGroupSummariesTable 5.09 kB
frontend/dist-report/posthog-app/_parent/products/session_summaries/frontend/SessionGroupSummaryScene 19.3 kB
frontend/dist-report/posthog-app/_parent/products/tasks/frontend/SlackTaskContextScene 8.91 kB
frontend/dist-report/posthog-app/_parent/products/tasks/frontend/TaskDetailScene 23.9 kB
frontend/dist-report/posthog-app/_parent/products/tasks/frontend/TaskTracker 14.7 kB
frontend/dist-report/posthog-app/_parent/products/tracing/frontend/TracingScene 67.3 kB
frontend/dist-report/posthog-app/_parent/products/user_interviews/frontend/UserInterview 9.41 kB
frontend/dist-report/posthog-app/_parent/products/user_interviews/frontend/UserInterviewResponse 7.83 kB
frontend/dist-report/posthog-app/_parent/products/user_interviews/frontend/UserInterviews 6.12 kB
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewIndexScene 2.59 kB
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewRunScene 44.8 kB
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewRunsScene 7.36 kB
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewSettingsScene 11.1 kB
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotHistoryScene 13.9 kB
frontend/dist-report/posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewSnapshotOverviewScene 19.6 kB
frontend/dist-report/posthog-app/_parent/products/workflows/frontend/TemplateLibrary/MessageTemplate 16.7 kB
frontend/dist-report/posthog-app/_parent/products/workflows/frontend/Workflows/WorkflowScene 104 kB
frontend/dist-report/posthog-app/_parent/products/workflows/frontend/WorkflowsScene 60.3 kB
frontend/dist-report/posthog-app/src/index 61.1 kB
frontend/dist-report/posthog-app/src/layout/panel-layout/ai-first/tabs/NavTabChat 7.26 kB
frontend/dist-report/posthog-app/src/lib/components/Cards/TextCard/TextCardMarkdownEditor 11.3 kB
frontend/dist-report/posthog-app/src/lib/components/MonacoDiffEditor 471 B
frontend/dist-report/posthog-app/src/lib/lemon-ui/LemonMarkdown/MermaidDiagram 2.29 kB
frontend/dist-report/posthog-app/src/lib/lemon-ui/LemonTextArea/LemonTextAreaMarkdown 876 B
frontend/dist-report/posthog-app/src/lib/lemon-ui/Link/Link 359 B
frontend/dist-report/posthog-app/src/lib/monaco/CodeEditorInline 866 B
frontend/dist-report/posthog-app/src/lib/monaco/vimMode 211 kB
frontend/dist-report/posthog-app/src/lib/ui/Button/ButtonPrimitives 426 B
frontend/dist-report/posthog-app/src/queries/nodes/WebVitals/WebVitals 7.55 kB
frontend/dist-report/posthog-app/src/queries/nodes/WebVitals/WebVitalsPathBreakdown 4.12 kB
frontend/dist-report/posthog-app/src/queries/schema 860 kB
frontend/dist-report/posthog-app/src/scenes/activity/explore/EventsScene 3.32 kB
frontend/dist-report/posthog-app/src/scenes/activity/explore/SessionsScene 4.72 kB
frontend/dist-report/posthog-app/src/scenes/activity/live/LiveEventsTable 5.62 kB
frontend/dist-report/posthog-app/src/scenes/agentic/AgenticAuthorize 5.87 kB
frontend/dist-report/posthog-app/src/scenes/approvals/ApprovalDetail 16.6 kB
frontend/dist-report/posthog-app/src/scenes/approvals/changeRequestsLogic 918 B
frontend/dist-report/posthog-app/src/scenes/audit-logs/AdvancedActivityLogsScene 42.1 kB
frontend/dist-report/posthog-app/src/scenes/AuthenticatedShell 173 kB
frontend/dist-report/posthog-app/src/scenes/authentication/AccountConnected 3.36 kB
frontend/dist-report/posthog-app/src/scenes/authentication/AgenticAccountMismatch 2.77 kB
frontend/dist-report/posthog-app/src/scenes/authentication/CLIAuthorize 11.8 kB
frontend/dist-report/posthog-app/src/scenes/authentication/CLILive 4.4 kB
frontend/dist-report/posthog-app/src/scenes/authentication/credential-review/CredentialReview 3.98 kB
frontend/dist-report/posthog-app/src/scenes/authentication/EmailMFAVerify 3.4 kB
frontend/dist-report/posthog-app/src/scenes/authentication/InviteSignup 15.4 kB
frontend/dist-report/posthog-app/src/scenes/authentication/Login 10.2 kB
frontend/dist-report/posthog-app/src/scenes/authentication/Login2FA 5.11 kB
frontend/dist-report/posthog-app/src/scenes/authentication/passkeyLogic 858 B
frontend/dist-report/posthog-app/src/scenes/authentication/PasswordReset 4.74 kB
frontend/dist-report/posthog-app/src/scenes/authentication/PasswordResetComplete 3.38 kB
frontend/dist-report/posthog-app/src/scenes/authentication/signup/SignupContainer 28.6 kB
frontend/dist-report/posthog-app/src/scenes/authentication/signup/verify-email/VerifyEmail 5.16 kB
frontend/dist-report/posthog-app/src/scenes/authentication/TwoFactorReset 4.41 kB
frontend/dist-report/posthog-app/src/scenes/authentication/VercelConnect 5.37 kB
frontend/dist-report/posthog-app/src/scenes/authentication/VercelLinkError 2.64 kB
frontend/dist-report/posthog-app/src/scenes/billing/AuthorizationStatus 1.1 kB
frontend/dist-report/posthog-app/src/scenes/billing/Billing 867 B
frontend/dist-report/posthog-app/src/scenes/billing/BillingSection 21.2 kB
frontend/dist-report/posthog-app/src/scenes/cohorts/Cohort 28.5 kB
frontend/dist-report/posthog-app/src/scenes/cohorts/CohortCalculationHistory 6.61 kB
frontend/dist-report/posthog-app/src/scenes/cohorts/Cohorts 9.81 kB
frontend/dist-report/posthog-app/src/scenes/coupons/Coupons 1.1 kB
frontend/dist-report/posthog-app/src/scenes/dashboard/Dashboard 1.68 kB
frontend/dist-report/posthog-app/src/scenes/dashboard/dashboards/Dashboards 19.9 kB
frontend/dist-report/posthog-app/src/scenes/dashboard/dashboards/templates/DashboardTemplateCopyScene 6.09 kB
frontend/dist-report/posthog-app/src/scenes/data-management/DataManagementScene 1.02 kB
frontend/dist-report/posthog-app/src/scenes/data-management/definition/DefinitionEdit 17.3 kB
frontend/dist-report/posthog-app/src/scenes/data-management/definition/DefinitionView 24.4 kB
frontend/dist-report/posthog-app/src/scenes/data-management/MaterializedColumns/MaterializedColumns 12 kB
frontend/dist-report/posthog-app/src/scenes/data-management/variables/SqlVariableEditScene 7.63 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/batch-exports/BatchExportScene 61 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/DataPipelinesNewScene 2.76 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/DestinationsScene 3.13 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/event-filtering/EventFilterScene 22.3 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/legacy-plugins/LegacyPluginScene 21 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/TransformationsScene 2.37 kB
frontend/dist-report/posthog-app/src/scenes/data-pipelines/WebScriptsScene 2.99 kB
frontend/dist-report/posthog-app/src/scenes/data-warehouse/DataWarehouseScene 1.75 kB
frontend/dist-report/posthog-app/src/scenes/data-warehouse/editor/EditorScene 1.52 kB
frontend/dist-report/posthog-app/src/scenes/debug/DebugScene 20.3 kB
frontend/dist-report/posthog-app/src/scenes/debug/hog/HogRepl 7.75 kB
frontend/dist-report/posthog-app/src/scenes/experiments/Experiment 211 kB
frontend/dist-report/posthog-app/src/scenes/experiments/Experiments 21.8 kB
frontend/dist-report/posthog-app/src/scenes/experiments/SharedMetrics/SharedMetric 6.45 kB
frontend/dist-report/posthog-app/src/scenes/experiments/SharedMetrics/SharedMetrics 923 B
frontend/dist-report/posthog-app/src/scenes/exports/ExportsScene 4.45 kB
frontend/dist-report/posthog-app/src/scenes/feature-flags/FeatureFlag 144 kB
frontend/dist-report/posthog-app/src/scenes/feature-flags/FeatureFlags 1.12 kB
frontend/dist-report/posthog-app/src/scenes/groups/Group 15.6 kB
frontend/dist-report/posthog-app/src/scenes/groups/Groups 4.29 kB
frontend/dist-report/posthog-app/src/scenes/groups/GroupsNew 7.73 kB
frontend/dist-report/posthog-app/src/scenes/health-alerts/HealthAlertsScene 4.17 kB
frontend/dist-report/posthog-app/src/scenes/health/categoryDetail/HealthCategoryDetailScene 7.64 kB
frontend/dist-report/posthog-app/src/scenes/health/HealthScene 12.8 kB
frontend/dist-report/posthog-app/src/scenes/health/pipelineStatus/PipelineStatusScene 11.5 kB
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapNewScene 5.41 kB
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapRecordingScene 4.31 kB
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapScene 6.94 kB
frontend/dist-report/posthog-app/src/scenes/heatmaps/scenes/heatmaps/HeatmapsScene 4.27 kB
frontend/dist-report/posthog-app/src/scenes/hog-functions/HogFunctionScene 55.3 kB
frontend/dist-report/posthog-app/src/scenes/inbox/InboxScene 63.4 kB
frontend/dist-report/posthog-app/src/scenes/insights/InsightQuickStart/InsightQuickStart 5.81 kB
frontend/dist-report/posthog-app/src/scenes/insights/InsightScene 34.8 kB
frontend/dist-report/posthog-app/src/scenes/insights/views/BoxPlot/BoxPlot 5.43 kB
frontend/dist-report/posthog-app/src/scenes/insights/views/CalendarHeatMap/CalendarHeatMap 4.87 kB
frontend/dist-report/posthog-app/src/scenes/insights/views/RegionMap/RegionMap 29.8 kB
frontend/dist-report/posthog-app/src/scenes/insights/views/WorldMap/WorldMap 5.16 kB
frontend/dist-report/posthog-app/src/scenes/instance/AsyncMigrations/AsyncMigrations 13.5 kB
frontend/dist-report/posthog-app/src/scenes/instance/DeadLetterQueue/DeadLetterQueue 5.77 kB
frontend/dist-report/posthog-app/src/scenes/instance/QueryPerformance/QueryPerformance 9 kB
frontend/dist-report/posthog-app/src/scenes/instance/SystemStatus/SystemStatus 17.4 kB
frontend/dist-report/posthog-app/src/scenes/IntegrationsRedirect/IntegrationsRedirect 1.11 kB
frontend/dist-report/posthog-app/src/scenes/marketing-analytics/MarketingAnalyticsScene 42.1 kB
frontend/dist-report/posthog-app/src/scenes/max/Max 1.06 kB
frontend/dist-report/posthog-app/src/scenes/models/ModelsScene 19.1 kB
frontend/dist-report/posthog-app/src/scenes/models/NodeDetailScene 17.1 kB
frontend/dist-report/posthog-app/src/scenes/moveToPostHogCloud/MoveToPostHogCloud 4.84 kB
frontend/dist-report/posthog-app/src/scenes/new-tab/NewTabScene 1.85 kB
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebookCanvasScene 3.96 kB
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebookPanel/NotebookPanel 6.01 kB
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebookScene 9.33 kB
frontend/dist-report/posthog-app/src/scenes/notebooks/NotebooksScene 7.98 kB
frontend/dist-report/posthog-app/src/scenes/oauth/OAuthAuthorize 1.01 kB
frontend/dist-report/posthog-app/src/scenes/onboarding/coupon/OnboardingCouponRedemption 1.58 kB
frontend/dist-report/posthog-app/src/scenes/onboarding/Onboarding 792 kB
frontend/dist-report/posthog-app/src/scenes/onboarding/sdks/SdkDoctorScene 10.2 kB
frontend/dist-report/posthog-app/src/scenes/organization/ConfirmOrganization/ConfirmOrganization 4.91 kB
frontend/dist-report/posthog-app/src/scenes/organization/Create/Create 1.03 kB
frontend/dist-report/posthog-app/src/scenes/organization/Deactivated 1.51 kB
frontend/dist-report/posthog-app/src/scenes/organization/PendingDeletion 2.48 kB
frontend/dist-report/posthog-app/src/scenes/persons/PersonScene 20.4 kB
frontend/dist-report/posthog-app/src/scenes/persons/PersonsScene 6.12 kB
frontend/dist-report/posthog-app/src/scenes/PreflightCheck/PreflightCheck 5.95 kB
frontend/dist-report/posthog-app/src/scenes/product-tours/ProductTour 275 kB
frontend/dist-report/posthog-app/src/scenes/product-tours/ProductTours 5.06 kB
frontend/dist-report/posthog-app/src/scenes/project-homepage/ProjectHomepage 19.1 kB
frontend/dist-report/posthog-app/src/scenes/project/Create/Create 1.21 kB
frontend/dist-report/posthog-app/src/scenes/resource-transfer/ResourceTransfer 9.56 kB
frontend/dist-report/posthog-app/src/scenes/saved-insights/SavedInsights 1.04 kB
frontend/dist-report/posthog-app/src/scenes/session-recordings/detail/SessionRecordingDetail 2.14 kB
frontend/dist-report/posthog-app/src/scenes/session-recordings/file-playback/SessionRecordingFilePlaybackScene 4.85 kB
frontend/dist-report/posthog-app/src/scenes/session-recordings/kiosk/SessionRecordingsKiosk 10.3 kB
frontend/dist-report/posthog-app/src/scenes/session-recordings/player/snapshot-processing/DecompressionWorkerManager 329 B
frontend/dist-report/posthog-app/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene 5.49 kB
frontend/dist-report/posthog-app/src/scenes/session-recordings/SessionRecordings 1.15 kB
frontend/dist-report/posthog-app/src/scenes/session-recordings/settings/SessionRecordingsSettingsScene 2.35 kB
frontend/dist-report/posthog-app/src/scenes/sessions/SessionProfileScene 15.6 kB
frontend/dist-report/posthog-app/src/scenes/settings/SettingsScene 3.94 kB
frontend/dist-report/posthog-app/src/scenes/sites/Site 1.9 kB
frontend/dist-report/posthog-app/src/scenes/startups/StartupProgram 21.6 kB
frontend/dist-report/posthog-app/src/scenes/StripeConfirmInstall/StripeConfirmInstall 3.92 kB
frontend/dist-report/posthog-app/src/scenes/subscriptions/SubscriptionScene 14.4 kB
frontend/dist-report/posthog-app/src/scenes/subscriptions/SubscriptionsScene 5.23 kB
frontend/dist-report/posthog-app/src/scenes/surveys/forms/SurveyFormBuilder 1.93 kB
frontend/dist-report/posthog-app/src/scenes/surveys/Survey 1.39 kB
frontend/dist-report/posthog-app/src/scenes/surveys/Surveys 26.8 kB
frontend/dist-report/posthog-app/src/scenes/surveys/wizard/SurveyWizard 72.8 kB
frontend/dist-report/posthog-app/src/scenes/themes/CustomCssScene 3.94 kB
frontend/dist-report/posthog-app/src/scenes/toolbar-launch/ToolbarLaunch 2.85 kB
frontend/dist-report/posthog-app/src/scenes/Unsubscribe/Unsubscribe 2.04 kB
frontend/dist-report/posthog-app/src/scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene 7.01 kB
frontend/dist-report/posthog-app/src/scenes/web-analytics/WebAnalyticsScene 14.8 kB
frontend/dist-report/posthog-app/src/scenes/wizard/Wizard 4.83 kB
frontend/dist-report/posthog-app/src/sharedChunkAnchors 1.19 kB
frontend/dist-report/render-query/src/render-query/render-query 27.4 MB
frontend/dist-report/toolbar/src/toolbar/toolbar 15.7 MB

compressed-size-action

Adds a real Modal e2e test for the ModalSandboxPool (opt-in by env, same
shape as real-inference.test.ts):

- Skips unless both MODAL_TOKEN_ID + MODAL_TOKEN_SECRET are present (in
  process env or repo-root .env).
- Provisions an actual Modal sandbox under a per-test app, lays out a
  trivial echo tool + dispatcher, exercises happy path + tool-not-loaded
  + action-not-found, asserts response shapes, terminates the sandbox.
- Loads SSL_CERT_FILE from /etc/ssl/cert.pem on darwin so the Modal
  gRPC handshake works (same workaround as real-inference.test.ts).

Caught a runtime bug while writing this:

- Modal's `exec()` rejects `timeoutMs: 0` with `timeoutMs must be
  positive` even though the type def claims "default 0 (no timeout)".
  The previous impl was passing `req.timeoutMs ?? 0` unconditionally,
  which would have broken every production tool call without an
  explicit timeout. Fix: only pass `timeoutMs` to exec when the caller
  supplied a positive value.

Runtime: ~7s end-to-end including Modal sandbox boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@benjackwhite

Copy link
Copy Markdown
Contributor Author

🤖 Comment from a Claude Code session, not Ben directly.

Follow-up: Ben asked whether I actually validated Modal end-to-end. I hadn't — only verified credentials worked and the bundle compiles. Pushed f7c8d26 which:

  1. Adds services/agent-shared/src/sandbox/sandbox-modal.test.ts — real Modal e2e (opt-in via MODAL_TOKEN_ID+MODAL_TOKEN_SECRET in env or repo-root .env, mirrors the real-inference.test.ts skip-when-no-creds pattern).
  2. Catches a real bug: Modal's exec() rejects timeoutMs: 0 with timeoutMs must be positive, even though the type def says "default 0 (no timeout)". The previous impl passed req.timeoutMs ?? 0 unconditionally — would have broken every production tool call without an explicit timeout. Fixed by only passing timeoutMs when positive.

The test ran green against Ben's Modal account in ~7s (provisions a sandbox, writes dispatch.js + a trivial echo tool, invokes happy-path + unknown-tool + unknown-action, asserts response shapes, terminates). One darwin-specific quirk handled in the test setup: same SSL_CERT_FILE=/etc/ssl/cert.pem workaround the real-inference suite needs, because Node's TLS doesn't read the macOS keychain.

…hans

Closes the loop the previous Modal commit left open: the
`agent_sandbox_instance` row already existed, but `provider_sandbox_id`
was stuffed with the runner's session UUID — useless for out-of-process
termination. If the runner pod crashes mid-session, the Modal sandbox
runs (and bills) until its own `timeoutMs` cap.

What lands here:

- `Sandbox` interface now requires `providerSandboxId`. Modal returns
  `handle.sandboxId` (the real `sb-...`); Docker returns the container
  hash; InProcess falls back to sessionId (no separate handle exists).
- Worker writes the real id into `agent_sandbox_instance.provider_sandbox_id`
  on `markReady` so out-of-process callers can use it.
- New `SandboxTerminator` abstraction in agent-shared, plus
  `MultiBackendSandboxTerminator` that routes by `SandboxKind`:
    - `modal`      → lazy-imports `modal`, calls
                     `client.sandboxes.fromId(id).terminate()`.
                     Idempotent — 404 / "not found" responses count as ok.
    - `in-process` → no-op (sandbox died with the runner that owned it).
    - `docker`     → no-op (janitor pod can't reach the runner's
                     docker socket; local-dev terminators can wire
                     their own).
- New sweep policy in agent-janitor:
    - `findStale(sandboxStaleMs)` → list `(provisioning|ready|terminating)`
      rows older than 10m (default).
    - Per row: call `terminator.terminate(kind, providerSandboxId)`.
      On success → `markTerminated`. On failure → leave the row alone
      so the next sweep tick retries.
    - SweepResult gains `reaped_sandboxes` + `sandbox_reap_failures`.
- `SANDBOX_STALE_MS` env var (default 10m = 2× the stuck-running
  threshold so a healthy session re-queue + resume cycle doesn't race
  the reaper).
- Janitor `index.ts` wires `PgSandboxInstanceStore` +
  `MultiBackendSandboxTerminator(createModalSandboxTerminator())` into
  the sweep deps. MODAL_TOKEN_ID/SECRET inherits from the existing
  shared `secret_env` block; no chart change needed.

Coverage:

- 5 new unit tests in `agent-janitor/src/sweep.test.ts` exercising
  reap-success, fresh-row no-op, terminator-failure retry path,
  missing-dep no-op, custom threshold.
- New real-Modal test case in
  `agent-shared/src/sandbox/sandbox-modal.test.ts`:
    - Asserts `providerSandboxId` matches Modal's `sb-...` shape.
    - Spawns a **fresh** terminator (no shared state with the original
      pool), terminates by id only — proves the row's
      provider_sandbox_id alone is enough to kill the compute
      out-of-process.
    - Confirms idempotency (second terminate of the same id → ok) and
      that the sandbox reports dead afterwards.
- Existing e2e janitor fixtures updated for the new SweepResult fields.

All 158 e2e cases + 116 janitor unit tests pass. Real-Modal tests both
green (~7s + ~5s) against Ben's account.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@benjackwhite benjackwhite requested a review from dmarticus June 4, 2026 08:19
benjackwhite and others added 7 commits June 4, 2026 10:46
Vitest's Vite layer searches upward for a postcss.config.js and hits
the repo-root one (Tailwind). In CI's filtered install
(`pnpm --filter '@posthog/agent-X...' install --frozen-lockfile`),
`@tailwindcss/postcss` isn't a transitive dep of any agent service and
the test run blows up at load with `Cannot find module
'@tailwindcss/postcss'`.

Backend services don't process CSS at all, so set
`css.postcss.plugins: []` per workspace to short-circuit the lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts the Modal sandbox impl from "v0 stub-replacement" to "production
shape" and converges Docker + Modal on the same per-session sandbox-host
image. Picks up the patterns from products/tasks/backend/services/modal_sandbox.py
that we'd otherwise rediscover the hard way.

Modal sandbox hardening (sandbox-modal.ts):
- Region pinning: MODAL_REGION env wins; otherwise derived from
  CLOUD_DEPLOYMENT (US→us-east, EU→eu-west, else→us-east). Matches the
  Tasks side so EU-tenant agents stay in EU.
- Named sandboxes: `name: agent-<sessionId-prefix>` so the Modal
  dashboard is browsable.
- CPU + memory caps: passed as reservation+limit pairs (Modal rejects
  one without the other), wired from new `spec.limits.max_memory_mb`
  (default 512 MiB) and `spec.limits.max_cpu_cores` (default 0.25).
- Provision diagnostics: `verbose: true` + a try/catch around
  `sandboxes.create` that logs image/region/cpu/mem with the error so a
  wedged image pull or scheduling failure shows up in our logs
  immediately instead of as a silent timeout.
- SANDBOX_HOST_IMAGE env supports a shared image reference; selector
  resolves backend-specific override → SANDBOX_HOST_IMAGE → backend
  default. Default stays `node:24-alpine` so dev keeps working; chart
  switches to the canonical image once the first CI build lands.

Canonical sandbox-host image (services/agent-sandbox-host/):
- Dockerfile + README rewritten to make the image's role explicit:
  baked `/sandbox/dispatch.js` + `/sandbox/host.js` consumed by BOTH
  pools. No CMD — Modal idles by default; the Docker pool overrides
  with `node /sandbox/host.js` for the alive-marker handshake.
- New `scripts/smoke-test-image.sh` containerized smoke test. One
  fresh container + workdir per scenario (avoids a macOS bind-mount
  cache race when re-using a single container across sequential
  dispatches). Asserts happy / bad-action / missing-tool. Hooked into
  the CI build step so a broken image fails before GHCR push.
- CI workflow adds posthog-agent-sandbox-host to both the build and
  deploy matrices; deploy dispatches a state.yaml row the chart
  consumes as `SANDBOX_HOST_IMAGE`.

Sandbox e2e parity (sandbox-{modal,docker}.test.ts):
- New `sandbox-docker.test.ts` mirrors the Modal e2e: provisions a real
  container from the canonical image, lays out a tool, dispatches,
  asserts response + providerSandboxId shape, releases. Opt-in by
  `docker info` + `docker image inspect` so CI without a daemon
  silently skips.
- Modal reaper test gets a poll-with-deadline on the post-terminate
  `isAlive()` check — Modal's `poll()` is eventually consistent (1-2s)
  and the test was occasionally racing the state update.

Spec (spec.ts):
- New `max_memory_mb` (default 512) and `max_cpu_cores` (default 0.25)
  on `SpecLimitsSchema`. Authors get sensible defaults; tools that
  load large model artifacts or are compute-bound can bump. Wired into
  Worker.acquireForSession's SandboxLimits.

All 158 e2e cases + 116 janitor unit tests + Modal e2e (×2) + Docker
e2e + containerized smoke test all green against the local build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups after the first CI run:

- **agent-shared / agent-janitor / agent-tools unit tests need
  SeaweedFS.** Their memory.test.ts (and tools/memory.test.ts) hit a
  real S3MemoryStore by design — see services/agent-shared/CLAUDE.md.
  Bring up the `seaweedfs` compose service in the unit-tests matrix
  via `bin/ci-wait-for-docker` and point `AGENT_MEMORY_TEST_S3_ENDPOINT`
  at it. Uniform across the matrix so we don't need per-workspace
  conditionals.
- **agent-migrations needed a vitest.config.ts.** Without one, vitest
  walks up to the repo-root postcss.config.js and tries to require
  `@tailwindcss/postcss` (same issue the other workspaces had — but
  agent-migrations had no config at all so the earlier fix didn't
  apply). Adds the same `css.postcss.plugins: []` stub +
  `passWithNoTests` so the script can stay `vitest run`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`@posthog/quill` ships `main: ./dist/index.cjs`, so tsc can't resolve
`import { … } from '@posthog/quill'` until quill is built. The build
job and Dockerfile already do this; the typecheck job was missing it,
producing TS2307 across every console source file that imports quill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…heck stub

CI run for the prior commit failed on the new sandbox-host image build:
  Error: failed to push 795637471508.dkr.ecr.us-east-1.amazonaws.com/posthog-agent-sandbox-host:
  unexpected status from POST: 404 Not Found

The ECR repo for posthog-agent-sandbox-host doesn't exist, and there's
no reason to create it — this image is consumed via GHCR by Modal and
by the chart-side SANDBOX_HOST_IMAGE env (both reach GHCR fine). The
chart doesn't deploy this image as a service so it doesn't need the
ECR-based image pipeline.

- `.github/actions/docker-meta/action.yml`: new `push-to-ecr` input
  (default `true`, no behavior change for existing callers). When
  `false`: skips configure-aws-credentials + amazon-ecr-login, omits
  the ECR tag from the images list. Aws-role-to-assume becomes
  optional in that case.
- `.github/workflows/ci-agent-container.yml`: matrix gets a
  `push_to_ecr` per image; sandbox-host sets `'false'`. The digest +
  smoke-test steps fall back to the GHCR ref when ECR is skipped so
  the pulled image still resolves.
- `services/agent-sandbox-host/package.json`: add a
  `typescript:check` script. The package is pure CJS (host.js +
  dispatch.js), so no real TS; node --check on the three .js files is
  the equivalent syntax check and lets the matrix's `typescript:check`
  step succeed for this workspace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-mount

Last CI build pushed the sandbox-host image to GHCR successfully (so the
push-to-ecr toggle works), but the smoke test failed:

  smoke: FAIL — host.alive never appeared

The image's CMD runs `node /sandbox/host.js` as the in-container
`sandbox` user, which writes `/workdir/host.alive`. On Linux the
bind-mounted host dir carries the runner's UID/GID; the in-container
non-root user can't write to it and exits silently. macOS Docker hides
this with VirtioFS UID translation, which is why the test passed
locally.

Fix: chmod the per-scenario tmpdir to world-writable before bind-mount.
The test owns the dir end to end so the looser perms are fine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The posthog-agent-sandbox-host image is now public on GHCR (verified —
Modal pulls anonymously). Switching the Modal pool to use it by default
and dropping the runtime dispatcher fallback now that the canonical
image bakes /sandbox/dispatch.js.

sandbox-modal.ts:
- DEFAULT_IMAGE_TAG → ghcr.io/posthog/posthog-agent-sandbox-host:master
  (mutable tag for dev; prod chart pins a digest).
- Drop the 80-line DISPATCH_JS string constant + the per-acquire
  writeText that handled the vanilla-node fallback. The image is now the
  contract; pointing at a base without /sandbox/dispatch.js will surface
  ENOENT at first invoke, by design.

sandbox-modal.test.ts:
- Both e2e tests honour SANDBOX_HOST_IMAGE env override so an in-flight
  PR can be validated against its `:pr-<num>` tag before `:master` exists.
- Validated against ghcr.io/posthog/posthog-agent-sandbox-host:pr-61452 —
  acquire/dispatch + reaper both green in ~11s.

posthog-agents smoke test fix (.github/scripts/smoke-test-agent-bundle.sh):
- The branch's earlier change made REDIS_URL required at boot. The CI
  smoke test wasn't passing it, so the ingress bundle fatal-exited on
  the missing var before reaching the dial-out stage the smoke test
  recognizes — flagged as "no recognisable boot output". Adds REDIS_URL
  + SANDBOX_BACKEND + MODAL_TOKEN_* + AGENT_USE_AI_GATEWAY +
  POSTHOG_AI_GATEWAY_URL + POSTHOG_API_BASE_URL to the env (all unreachable
  127.0.0.1:1 values; the bundle proceeds past config gates and fails on
  ECONNREFUSED, which the smoke test already recognizes). All four
  entrypoints (ingress/runner/janitor/migrate) pass locally against the
  published ghcr.io/posthog/posthog-agents:pr-61452 image.

services/agents/scripts/smoke-local.sh (new):
- One-command "build + smoke all four entrypoints" wrapper around the CI
  smoke script. SKIP_BUILD=1 reuses an existing tag; IMAGE=... smokes a
  specific reference (handy for validating a published PR image without
  a local rebuild).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@benjackwhite benjackwhite marked this pull request as ready for review June 4, 2026 11:50
@assign-reviewers-posthog assign-reviewers-posthog Bot requested a review from a team June 4, 2026 11:50
@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
services/agent-shared/src/sandbox/sandbox-modal.ts:194-211
Stale rejected promise permanently breaks the pool — if the first `acquireForSession` call fails due to a transient Modal auth error, rate limit, or network blip, `this.clientPromise` is set to a rejected `Promise`. Because the guard is `if (!this.clientPromise)`, every subsequent call skips re-initialization and immediately rejects with the same original error. The pool stays broken until the runner pod restarts. Clearing `clientPromise` on rejection lets the next acquire retry the handshake.

```suggestion
    private async getClient(): Promise<{ client: ModalClientType; app: App; image: Image }> {
        if (!this.clientPromise) {
            this.clientPromise = (async () => {
                // Dynamic import — keeps `modal` (gRPC + protobuf, heavy) off
                // the load path for tests / packages that never construct a
                // ModalSandboxPool. The selector imports this module
                // unconditionally; the SDK is paid for only when chosen.
                const { ModalClient } = await import('modal')
                const client = new ModalClient()
                const app = await client.apps.fromName(this.opts.appName ?? DEFAULT_APP_NAME, {
                    createIfMissing: true,
                })
                const image = client.images.fromRegistry(this.opts.image ?? DEFAULT_IMAGE_TAG)
                return { client, app, image }
            })().catch((err) => {
                // Clear so the next acquire can retry rather than
                // permanently failing with the same transient error.
                this.clientPromise = null
                throw err
            })
        }
        return this.clientPromise
    }
```

### Issue 2 of 2
services/agent-shared/src/sandbox/sandbox-modal.ts:118
The JSDoc says `ap-...` but `ap-` is Modal's App ID prefix, not the Sandbox ID prefix. Modal Sandbox IDs start with `sb-`, which is what `client.sandboxes.fromId()` actually expects — the e2e test at line 625 of `sandbox-modal.test.ts` confirms this with `expect(sandbox.providerSandboxId).toMatch(/^sb-/)`. The wrong prefix in the comment will mislead anyone debugging the janitor's reaper or checking the `provider_sandbox_id` column in the DB.

```suggestion
    /** Modal's `sb-...` sandbox id — what `client.sandboxes.fromId()` consumes. */
```

Reviews (1): Last reviewed commit: "feat(agents): switch Modal pool to canon..." | Re-trigger Greptile

Comment on lines +194 to 211
private async getClient(): Promise<{ client: ModalClientType; app: App; image: Image }> {
if (!this.clientPromise) {
this.clientPromise = (async () => {
// Dynamic import — keeps `modal` (gRPC + protobuf, heavy) off
// the load path for tests / packages that never construct a
// ModalSandboxPool. The selector imports this module
// unconditionally; the SDK is paid for only when chosen.
const { ModalClient } = await import('modal')
const client = new ModalClient()
const app = await client.apps.fromName(this.opts.appName ?? DEFAULT_APP_NAME, {
createIfMissing: true,
})
const image = client.images.fromRegistry(this.opts.image ?? DEFAULT_IMAGE_TAG)
return { client, app, image }
})()
}
return this.clientPromise
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Stale rejected promise permanently breaks the pool — if the first acquireForSession call fails due to a transient Modal auth error, rate limit, or network blip, this.clientPromise is set to a rejected Promise. Because the guard is if (!this.clientPromise), every subsequent call skips re-initialization and immediately rejects with the same original error. The pool stays broken until the runner pod restarts. Clearing clientPromise on rejection lets the next acquire retry the handshake.

Suggested change
private async getClient(): Promise<{ client: ModalClientType; app: App; image: Image }> {
if (!this.clientPromise) {
this.clientPromise = (async () => {
// Dynamic import — keeps `modal` (gRPC + protobuf, heavy) off
// the load path for tests / packages that never construct a
// ModalSandboxPool. The selector imports this module
// unconditionally; the SDK is paid for only when chosen.
const { ModalClient } = await import('modal')
const client = new ModalClient()
const app = await client.apps.fromName(this.opts.appName ?? DEFAULT_APP_NAME, {
createIfMissing: true,
})
const image = client.images.fromRegistry(this.opts.image ?? DEFAULT_IMAGE_TAG)
return { client, app, image }
})()
}
return this.clientPromise
}
private async getClient(): Promise<{ client: ModalClientType; app: App; image: Image }> {
if (!this.clientPromise) {
this.clientPromise = (async () => {
// Dynamic import — keeps `modal` (gRPC + protobuf, heavy) off
// the load path for tests / packages that never construct a
// ModalSandboxPool. The selector imports this module
// unconditionally; the SDK is paid for only when chosen.
const { ModalClient } = await import('modal')
const client = new ModalClient()
const app = await client.apps.fromName(this.opts.appName ?? DEFAULT_APP_NAME, {
createIfMissing: true,
})
const image = client.images.fromRegistry(this.opts.image ?? DEFAULT_IMAGE_TAG)
return { client, app, image }
})().catch((err) => {
// Clear so the next acquire can retry rather than
// permanently failing with the same transient error.
this.clientPromise = null
throw err
})
}
return this.clientPromise
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: services/agent-shared/src/sandbox/sandbox-modal.ts
Line: 194-211

Comment:
Stale rejected promise permanently breaks the pool — if the first `acquireForSession` call fails due to a transient Modal auth error, rate limit, or network blip, `this.clientPromise` is set to a rejected `Promise`. Because the guard is `if (!this.clientPromise)`, every subsequent call skips re-initialization and immediately rejects with the same original error. The pool stays broken until the runner pod restarts. Clearing `clientPromise` on rejection lets the next acquire retry the handshake.

```suggestion
    private async getClient(): Promise<{ client: ModalClientType; app: App; image: Image }> {
        if (!this.clientPromise) {
            this.clientPromise = (async () => {
                // Dynamic import — keeps `modal` (gRPC + protobuf, heavy) off
                // the load path for tests / packages that never construct a
                // ModalSandboxPool. The selector imports this module
                // unconditionally; the SDK is paid for only when chosen.
                const { ModalClient } = await import('modal')
                const client = new ModalClient()
                const app = await client.apps.fromName(this.opts.appName ?? DEFAULT_APP_NAME, {
                    createIfMissing: true,
                })
                const image = client.images.fromRegistry(this.opts.image ?? DEFAULT_IMAGE_TAG)
                return { client, app, image }
            })().catch((err) => {
                // Clear so the next acquire can retry rather than
                // permanently failing with the same transient error.
                this.clientPromise = null
                throw err
            })
        }
        return this.clientPromise
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread services/agent-shared/src/sandbox/sandbox-modal.ts Outdated
benjackwhite and others added 2 commits June 4, 2026 14:53
…JSDoc

Two issues Greptile flagged on the latest commit, plus the regenerated
OpenAPI artifacts the spec.limits.max_memory_mb + max_cpu_cores additions
from 964df72 should have produced (lint-staged regenerated them this
commit).

1. (P1) Stale rejected promise wedged the pool until pod restart. If the
   FIRST `acquireForSession` call rejected on the lazy client/app/image
   init (transient Modal auth blip, rate limit, network), the rejected
   promise stayed cached in `this.clientPromise`. The
   `if (!this.clientPromise)` guard skipped re-initialization on every
   subsequent call, so the pool kept rethrowing the same stale error
   forever. Clearing the field on rejection lets the next acquire retry
   the handshake.

2. (P2) JSDoc on `providerSandboxId` getter said `ap-...`, which is
   Modal's App ID prefix — Sandbox IDs start with `sb-`. The test
   regex already asserts `/^sb-/`; only the comment was stale.

3. Regenerated `spec_schema.py` (Django), `api.zod.ts` (frontend), and
   `api.ts` (MCP) from `hogli build:openapi` — picks up the new
   `max_memory_mb` (default 512) + `max_cpu_cores` (default 0.25)
   fields in `SpecLimitsSchema`. The lint-staged hook produced these
   when the previous commit's spec change was reformatted but the
   regen step didn't fire then.

Both Modal e2e tests still green against pr-61452 image.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tests-posthog

tests-posthog Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Query snapshots: Backend query snapshots updated

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

What this means:

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

Next steps:

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

Review snapshot changes →

@mendral-app mendral-app 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.

Supply Chain Security Review

⚠️ Review recommended — 1 finding in 1 file

All 14 added packages published 7+ days ago with no new CVEs or MAL- findings. Workflow changes use SHA-pinned actions and look well-structured. The only notable concern is the unpinned base image in the new Dockerfile.

Tag @mendral-app with feedback or questions. View session

Comment thread services/agent-sandbox-host/Dockerfile Outdated
# identical content) AND against a plain node base if the chart ever points
# at one.

FROM node:24-alpine

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 (P2): Base image node:24-alpine uses a floating tag. A compromised or unexpected upstream push could silently change the image contents between builds. Pin to a digest for reproducibility.

Suggested change
Suggested change
FROM node:24-alpine
FROM node:24-alpine@sha256:e2a4f5759776190b81bde04a498a462aad85810369e929ca8254f4c167b2bb8c
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/agent-sandbox-host/Dockerfile, line 19:

<issue>
Base image `node:24-alpine` uses a floating tag. A compromised or unexpected upstream push could silently change the image contents between builds. Pin to a digest for reproducibility.
</issue>

benjackwhite and others added 2 commits June 4, 2026 15:00
… opts, selector

Regression + unit coverage for the things the prior commits left bare:

1. **Greptile P1 lock-in.** Mocks `ModalClient` to throw on first call,
   asserts the second `acquireForSession` invokes the constructor again
   instead of returning the cached rejected promise. Without the
   catch-and-clear in `getClient()`, `expect(ctor).toHaveBeenCalledTimes(2)`
   would fail with `1` — proves the fix is load-bearing.

2. **`resolveRegion` exported + tested.** Covers MODAL_REGION env wins,
   CLOUD_DEPLOYMENT US/EU map correctly, unknown deployment falls back to
   us-east, unset falls back to us-east, MODAL_REGION beats CLOUD_DEPLOYMENT
   when both set. Was previously a module-private helper consumed only
   through the e2e.

3. **`timeoutMs: 0` regression.** I hit this exact bug on the first
   real-Modal run — Modal rejects `exec({ timeoutMs: 0 })` despite the
   type def claiming "default 0 (no timeout)". Asserts the field is
   *omitted* when undefined or zero, and passed through when positive.
   Without this, a refactor that reverts to `timeoutMs: req.timeoutMs ?? 0`
   would silently re-break.

4. **`selectSandboxPool` fail-fast + image fallback.** Throws clearly when
   SANDBOX_BACKEND is unset or set to `in-process` (the intentionally-
   removed case). SANDBOX_HOST_IMAGE applies to both pools as the shared
   fallback. Backend-specific overrides (SANDBOX_MODAL_IMAGE /
   SANDBOX_DOCKER_IMAGE) beat the shared one.

All tests use top-level imports + `vi.doMock` — the dynamic
`await import(...)` workaround in the first draft was unnecessary;
`vi.doMock` takes effect for the dynamic `await import('modal')` inside
`getClient()` at call time, not at the time `sandbox-modal` itself is
imported by the test file.

10 unit tests, ~250ms total. Full sandbox suite (28 tests including the
two real-Modal e2e + the real-Docker e2e) green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes a working rule into agent-tests/CLAUDE.md so it lives in the
repo (and auto-loads when working anywhere under services/agent-*)
instead of one engineer's personal config:

- Bug fix → regression test (must fail when fix is reverted; mental-
  trace counts when reverting is annoying because of formatter
  rewrites or hooks).
- New helper / pure function → unit test for the obvious axes.
- New plumbing → wire test that asserts the value lands at the
  downstream consumer.
- E2E doesn't substitute for unit tests — harness covers
  end-to-end feature, unit tests cover single-function contract.
- Run the relevant file before declaring done. "Wrote a test, didn't
  run it" is how broken assertions ship.

Plus the vitest mock trap call-out: top-level imports + `vi.doMock`
is the right pattern, not `await import` inside `it` blocks — the
mock registry is consulted when the SUT's dynamic import runs, not
when the test file imports the SUT. (Worked example:
services/agent-shared/src/sandbox/sandbox-modal-unit.test.ts.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@mendral-app mendral-app 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.

Supply Chain Security Review

✅ Approve — 1 finding in 1 file

14 packages added, all published 7+ days ago with no new CVEs or MAL- findings. GitHub Actions remain pinned to commit SHAs. The modal npm package addition aligns with the stated sandbox-pool feature.

Tag @mendral-app with feedback or questions. View session

@@ -1,23 +1,41 @@
# Canonical sandbox-host image used by BOTH the Docker and Modal pools.

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 (P2): The node:24-alpine tag is mutable; now that this Dockerfile is built and pushed in CI, pin to a digest to ensure reproducible builds and prevent silent base-image substitution.

Suggested change
Suggested change
# Canonical sandbox-host image used by BOTH the Docker and Modal pools.
FROM node:24-alpine@sha256:b39b36df10420d52576a1b22b50b29aed1264ab576a89fae9388cd7a363dd8d2
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/agent-sandbox-host/Dockerfile, line 1:

<issue>
The `node:24-alpine` tag is mutable; now that this Dockerfile is built and pushed in CI, pin to a digest to ensure reproducible builds and prevent silent base-image substitution.
</issue>

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

🔍 Migration Risk Analysis

We've analyzed your migrations for potential risks.

Summary: 2 Safe | 1 Needs Review | 0 Blocked

⚠️ Needs Review

May have performance impact

agent_platform.0003_agentskilltemplate_spec_frontmatter
  └─ #1 ⚠️ AlterField
     Field alteration may cause table locks or data loss (check if changing type or constraints)
     model: agentskilltemplate, field: name, field_type: CharField
  └─ #2 ⚠️ AlterField
     Field alteration may cause table locks or data loss (check if changing type or constraints)
     model: agentskilltemplate, field: description, field_type: CharField
  └─ #3 ✅ AddField
     Adding NOT NULL field with constant default (safe in PG11+)
     model: agentskilltemplate, field: license
  └─ #4 ✅ AddField
     Adding NOT NULL field with constant default (safe in PG11+)
     model: agentskilltemplate, field: compatibility

✅ Safe

Brief or no lock, backwards compatible

agent_platform.0001_initial
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: AgentApplication
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: AgentRevision
  └─ #3 ✅ AddField
     Adding nullable field requires brief lock
     model: agentapplication, field: live_revision
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).
agent_platform.0002_agentcustomtooltemplate_and_more
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: AgentCustomToolTemplate
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: AgentRevisionCustomToolTemplate
  └─ #3 ✅ CreateModel
     Creating new table is safe
     model: AgentRevisionNativeTool
  └─ #4 ✅ CreateModel
     Creating new table is safe
     model: AgentSkillTemplate
  └─ #5 ✅ CreateModel
     Creating new table is safe
     model: AgentRevisionSkillTemplate
  └─ #6 ✅ CreateModel
     Creating new table is safe
     model: AgentSkillTemplateFile
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

📚 How to Deploy These Changes Safely

AddField:

This operation acquires a brief lock but doesn't rewrite the table.

Deployment uses lock timeouts with automatic retries, so lock contention will cause retries rather than connection pile-up.

Last updated: 2026-06-04 17:26 UTC (dd45070)

The repo-wide semgrep-general job excludes services/, so every
services/agent-*/ (and services/agents/) silently dropped out of SAST
and tripped the WF004-semgrep-services-coverage workflow lint check.
Add them all to the semgrep-js target list.

Bringing them under scan surfaced pre-existing benign findings — local
dev/test redis:// defaults, a docstring URL, the Aurora-CA pg pool
ssl setting, and an example seed script's urllib call. Suppress each
with a documented `// nosemgrep` per the existing services/mcp
convention rather than weakening the rules.

Also pin the sandbox-host base image to node:24.13.0-alpine (matching
services/agents/Dockerfile's NODE_VERSION) so it can't silently shift
between builds, addressing the floating-tag review note.

Generated-By: PostHog Code
Task-Id: 3441c90a-dda9-4317-bf06-83ad39346d1e
@dmarticus dmarticus requested a review from a team as a code owner June 4, 2026 17:15
@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
High CVE: Undici: Malicious WebSocket 64-bit length overflows parser and crashes the client

CVE: GHSA-f269-vfmq-vjvj Undici: Malicious WebSocket 64-bit length overflows parser and crashes the client (HIGH)

Affected versions: >= 6.0.0 < 6.24.0; >= 7.0.0 < 7.24.0

Patched version: 7.24.0

From: pnpm-lock.yamlnpm/undici@7.18.2

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@7.18.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Undici has Unhandled Exception in WebSocket Client Due to Invalid server_max_window_bits Validation

CVE: GHSA-v9p9-hfj2-hcw8 Undici has Unhandled Exception in WebSocket Client Due to Invalid server_max_window_bits Validation (HIGH)

Affected versions: < 6.24.0; >= 7.0.0 < 7.24.0

Patched version: 7.24.0

From: pnpm-lock.yamlnpm/undici@7.18.2

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@7.18.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Undici has Unbounded Memory Consumption in WebSocket permessage-deflate Decompression

CVE: GHSA-vrm6-8vpv-qv8q Undici has Unbounded Memory Consumption in WebSocket permessage-deflate Decompression (HIGH)

Affected versions: < 6.24.0; >= 7.0.0 < 7.24.0

Patched version: 7.24.0

From: pnpm-lock.yamlnpm/undici@7.18.2

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@7.18.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

if (allowlist.length && !allowlist.includes(u.host)) {
throw new Error(`host not allowed: ${u.host}`)
}
const res = await fetch(args.url, { method: 'GET' })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

High: Server-side request forgery

@posthog/web-fetch runs as a native tool inside the runner process, so this direct fetch(args.url) lets an agent invoke arbitrary URLs from the runner's network position and return the response body. Route this through an explicit egress proxy/dispatcher or validate and block private, link-local, and cluster-local destinations here; Node's global fetch will not enforce that just because the tool description says infrastructure does.

@veria-ai

veria-ai Bot commented Jun 4, 2026

Copy link
Copy Markdown

PR overview

This PR wires the agents system for production readiness, including Modal sandbox integration, a Redis-only bus, approval flows, and gateway-related plumbing. It also includes a native web-fetch tool used by agents to retrieve URL contents.

There is one open security issue: the agent web-fetch tool can directly request arbitrary URLs from the runner’s network position and return the response body. This creates a meaningful server-side request forgery risk because internal, private, or cluster-local services may be reachable unless egress is proxied or destinations are explicitly blocked. No issues have been fixed yet, so the PR still needs a targeted mitigation before it is in a safe security posture.

Open issues (1)

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

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

🎭 Playwright report · View test results →

⚠️ 2 flaky tests:

  • Inline editing insight title via compact card popover (chromium)
  • Save an insight, make changes, discard them, and save a copy (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this comment? Help fix flakies and failures and it'll disappear!

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

gonna stamp and merge this

@dmarticus dmarticus merged commit dde0be6 into ass Jun 4, 2026
244 of 252 checks passed
@dmarticus dmarticus deleted the agents-prod-readiness branch June 4, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants