run-posthog — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited run-posthog (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
PostHog is Django + Vite + Celery + plugin-server, backed by Postgres, ClickHouse, Kafka, Redis, and Temporal in Docker, fronted by an Envoy-style proxy at http://localhost:8010. The dev stack runs in detached mode under phrocs so the running processes are inspectable from this session via the phrocs MCP server. Browser MCP servers (chrome-devtools-mcp, playwright) drive the UI; nothing about this skill ships its own driver.
For `/run`: get the app reachable so the user can drive it. Success = http://localhost:8010 serves and the core units are ready. Do not chase crashed migration units, do not seed data unless asked.
For `/verify`: build (Vite HMR handles this automatically for frontend; backend changes need no build), run the same launch as /run, then drive the live app via a browser MCP to observe the change. The observation is the verification — tests and type checks don't substitute.
All paths below are relative to the repo root.
curl -L https://downloads.flox.dev/by-env/stable/install.sh | sudo bashbrew install --cask orbstack)brew install 1password-cli, only if .env.local contains op:// refshogli dev:setup # first time only — interactive wizard picks which workers to run
hogli up -d -y # start the stack detached under phrocs
hogli services:ready -y # wait for Docker services (Postgres, CH, Kafka, Redis)
hogli wait -y # wait for phrocs-managed units (Django, Vite, Celery, plugin-server)
hogli doctor # optional: stale migrations, zombie phrocs, disk pressureFirst boot is 60-90s while Django imports and Vite warms. Stop with hogli down -y (leaves Docker services running for fast restart).
There are two readiness bars. Don't conflate them.
Ready for `/run` — the app is reachable and you can drive it:
curl -sf http://localhost:8010/_health # 200
curl -sf -o /dev/null -w '%{http_code}' http://localhost:8010/ # 200 or 302
curl -sS http://localhost:8010/api/projects/@current | grep -q '"code":"not_authenticated"' # API + DB reachablePlus these phrocs units ready:true: backend, frontend, nodejs, capture, ingestion. Stop here. *Do not chase crashed `migrate- units when only /run` was asked** — the stack is usable for launch, screenshot, and most UI scenes (home, login, settings, feature flags) regardless of migration state.
Ready for `/verify` of HogQL-backed scenes (insights, dashboards, web analytics) and for POST /api/setup_test/...:
mcp__phrocs__get_process_status process="migrate-clickhouse" to show status:"done" exit_code:0. If it's crashed, see the gotcha below.phrocs MCP tools — for either bar:
mcp__phrocs__get_process_status (no args) — all units, with ready, exit_code, memory, CPU.mcp__phrocs__get_process_status process="frontend" — single unit.mcp__phrocs__get_process_logs process="backend" — tail recent stdout/stderr.mcp__phrocs__toggle_process process="<unit>" — restart one unit. Auto-mode blocks this on shared stacks (treated as restart-of-shared-infra). If blocked, ask the user to approve, or run phrocs stop && hogli up -d for a full clean restart.Every empty PostHog scene looks broken because no events exist. To verify a UI change, you need a workspace with realistic data. The canonical path is the same one the Playwright suite uses: POST /api/setup_test/organization_with_team/. Gated on DEBUG=True | E2E_TESTING | CI | TEST, all of which local dev satisfies via DEBUG=True. Implementation: posthog/api/playwright_setup.py + posthog/test/playwright_setup_functions.py:create_organization_with_team — 3 clusters via HedgeboxMatrix, ~5-10s end to end. Reference call site: playwright/utils/playwright-setup.ts:251.
Avoid hogli dev:demo-data for /run and /verify — it calls generate_demo_data with n_clusters=500 (default at posthog/management/commands/generate_demo_data.py:59) and takes 5-30 minutes. It exists for humans who want a big realistic dataset to play with; it's the wrong tool for automated launch-and-screenshot.
The full browser-MCP recipe:
new_page http://localhost:8010/login.evaluate_script the workspace bootstrap. Per-call email so reruns don't collide; password fixed at 12345678; response gives back the team_id and a personal API key. const r = await fetch('/api/setup_test/organization_with_team/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: { skip_onboarding: true } }),
})
const { result } = await r.json()
// result: { user_email, team_id, personal_api_key, organization_id, ... }
return resultevaluate_script the in-page login. Runs in the page context so Django's CSRF middleware sees the right cookies (playwright/utils/playwright-setup.ts:282-291). const r = await fetch('/api/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: workspace.user_email, password: '12345678' }),
})
return r.status // 200 = logged in; 403 = you're not in-page; 400 = workspace setup didn't actually create the usernavigate_page to http://localhost:8010/project/{team_id}{scene-path}.wait_for text or a [data-attr] element. PostHog attaches data-attr to virtually every rendered element; use it as the "page hydrated" signal. networkidle and load never settle because PostHog.js and Vite HMR keep polling.take_screenshot. The accessibility snapshot from wait_for is also enough on its own for most "is this element present?" verifications — no pixel comparison needed.For API-only calls (creating fixtures, hitting endpoints without a browser), use the personal_api_key from the setup_test response as Authorization: Bearer <key> — no login dance.
For deeper interaction (clicking, filling forms, inspecting console), the same MCP toolkit (chrome-devtools-mcp:* / playwright:*) covers it. No project-specific wrapper needed.
The frontend uses kea-router. The mapping rule:
| Edited path | Scene URL (under /project/{team_id}/) |
|---|---|
frontend/src/scenes/<name>/** | usually /<name> (e.g. insights/ → /insights) |
frontend/src/scenes/activity/** | /activity/explore (and other ActivityTabs) |
frontend/src/scenes/data-management/** | /data-management/<sub> |
frontend/src/scenes/settings/** | /settings/<section> |
frontend/src/scenes/authentication/** | /login, /signup, /preflight (un-scoped) |
products/<name>/frontend/** | per the urls: block in products/<name>/manifest.tsx |
frontend/src/layout/** | renders everywhere; verify on any scene |
frontend/src/lib/components/<X>.tsx | grep usages, screenshot the scene that mounts it |
Canonical sources to grep when the path isn't obvious:
frontend/src/scenes/urls.ts — top-level URL helpers (urls.insights(), urls.dashboard(...), etc.).frontend/src/scenes/scenes.ts — scene-to-route registry.products/<name>/manifest.tsx — per-product routes.migrate-postgres, and if Postgres isn't ready yet they crash. This is the hard prereq for POST /api/setup_test/... and for HogQL-backed scenes (insights, dashboards, web analytics) — but not for /run. Don't fix it unless the task needs HogQL/setup_test. When you do need to fix it, the canonical sequence is: wait for migrate-postgres to show status:"done" via mcp__phrocs__get_process_status, then restart the crashed migrations. mcp__phrocs__toggle_process is the surgical tool but auto-mode blocks it on shared stacks — fall back to phrocs stop && hogli up -d which re-runs everything in order, or run python manage.py migrate_clickhouse directly (you'll need set -a; source .env.services; set +a first so CLICKHOUSE_DATABASE=posthog, otherwise it targets default). If neither works (corrupted CH replica state in ZooKeeper from a partial run), hogli dev:reset is the only path — it wipes Docker volumes, destructive.mcp__phrocs__get_process_status or the curl probes.http://localhost:8010 (the Envoy-style proxy). The proxy reverse-proxies Vite for /static/* and Django for everything else. Hitting :8234/ directly returns 404 because Vite has no index route at the dev-server root.posthog-clickhouse-1 / posthog-db-1 containers, so DB state is global. But ports 8000/8010/8234 can only be held by one worktree at a time — kill the granian/vite/phrocs of the other worktree before hogli up -d here./api/projects/@current, /api/users/@me/, and PostHog.js remote config — all 401 until you sign up. WASM/CSP "Report Only" warnings come from the dev CSP.browser_evaluate so cookies and CSRF tokens flow. For non-browser API calls, use the personal_api_key from the setup_test response as Authorization: Bearer <key> — no CSRF on token auth.op installed, refs become literal strings (e.g. OPENAI_API_KEY=op://...) and downstream services fail with cryptic auth errors. Install op or replace with literals.mcp__phrocs__get_process_status shows what's there; if nothing's running, rm bin/start.lock and retry.open -a OrbStack.docker ps | grep posthog- and look for unhealthy containers; hogli services:ready waits for all of them.setup_test workspace didn't actually create (CH crash usually). Check the response of the setup_test call; if it 500'd, fix CH first (see the migrate-clickhouse gotcha).DEBUG, E2E_TESTING, CI, and TEST are all false. Local dev has DEBUG=True by default; if it's not set, .env.local is missing or DJANGO_SETTINGS_MODULE points at a prod-like settings module.migrate-clickhouse crashed during boot. See the gotcha above.page.request.post rather than page.evaluate). Use evaluate_script / browser_evaluate so the call originates in-page.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.