run-posthog
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePostHog is Django + Vite + Celery + plugin-server, backed by Postgres, ClickHouse, Kafka, Redis, and Temporal in Docker, fronted by an Envoy-style proxy at . The dev stack runs in detached mode under so the running processes are inspectable from this session via the MCP server. Browser MCP servers (, ) drive the UI; nothing about this skill ships its own driver.
http://localhost:8010phrocsphrocschrome-devtools-mcpplaywrightFor : get the app reachable so the user can drive it. Success = serves and the core units are . Do not chase crashed migration units, do not seed data unless asked.
/runhttp://localhost:8010readyFor : build (Vite HMR handles this automatically for frontend; backend changes need no build), run the same launch as , 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. For frontend/UI changes, the deep version of this workflow is the repo-local skill in local mode: scenario design from the diff, reproducibility retries, and annotated evidence.
/verify/runqa-frontendAll paths below are relative to the repo root.
PostHog 基于 Django + Vite + Celery + plugin-server 构建,底层依赖Docker中的Postgres、ClickHouse、Kafka、Redis和Temporal,前端通过位于的Envoy风格代理提供访问。开发栈在下以分离模式运行,因此可以通过 MCP服务器在当前会话中查看运行中的进程。浏览器MCP服务器(、)用于驱动UI;本技能不自带任何驱动程序。
http://localhost:8010phrocsphrocschrome-devtools-mcpplaywright对于 :使应用可访问,以便用户能够操作它。成功标准 = 可正常访问且核心单元处于状态。无需排查崩溃的迁移单元,除非明确要求,否则不要植入数据。
/runhttp://localhost:8010ready对于 :构建(前端由Vite HMR自动处理;后端变更无需构建),执行与相同的启动流程,然后通过浏览器MCP驱动实时应用以观察变更。观察结果即为验证依据——测试和类型检查不能替代。对于前端/UI变更,该工作流的深度版本是仓库本地的技能(本地模式):基于diff设计场景、可复现性重试,以及带注释的证据。
/verify/runqa-frontend以下所有路径均相对于仓库根目录。
Prerequisites
前置条件
- flox 1.12+ provisions the toolchain —
curl -L https://downloads.flox.dev/by-env/stable/install.sh | sudo bash - Docker — OrbStack preferred on macOS ()
brew install --cask orbstack - 1Password CLI (optional) — , only if
brew install 1password-clicontains.env.localrefsop://
- flox 1.12及以上版本用于提供工具链——
curl -L https://downloads.flox.dev/by-env/stable/install.sh | sudo bash - Docker — macOS上推荐使用OrbStack()
brew install --cask orbstack - 1Password CLI(可选)——,仅当
brew install 1password-cli包含.env.local引用时需要op://
Launch
启动
For a normal developer stack, use the saved interactive setup:
bash
hogli 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 # broad all-process signal; confirm with the checks below
hogli doctor # optional: stale migrations, zombie phrocs, disk pressureFor agent-managed QA, use the normal background stack unless the user asks to tune services. On a devbox, prefer for a new/stopped box; if it is already running without the app, start PostHog inside it:
hogli devbox:start --start-appbash
hogli devbox:exec -- bash -lc 'cd ~/posthog && ./bin/hogli up -d -y'Use , , and only to understand route-specific dependencies; custom generated configs are not the default QA startup path.
hogli dev:intentshogli dev:explain <intent>hogli dev:list-units <intent>First boot is 60-90s while Django imports and Vite warms. Stop with (leaves Docker services running for fast restart).
hogli down -y对于常规开发栈,使用预设的交互式设置:
bash
hogli dev:setup # 首次执行——交互式向导选择要运行的worker
hogli up -d -y # 在phrocs下以分离模式启动栈
hogli services:ready -y # 等待Docker服务就绪(Postgres、CH、Kafka、Redis)
hogli wait -y # 广义的全进程信号;通过以下检查确认
hogli doctor # 可选:检查过时的迁移、僵尸phrocs进程、磁盘压力对于Agent管理的QA,除非用户要求调整服务,否则使用常规后台栈。在devbox上,对于新的/已停止的盒子,优先使用;如果它已经在运行但没有启动应用,则在其中启动PostHog:
hogli devbox:start --start-appbash
hogli devbox:exec -- bash -lc 'cd ~/posthog && ./bin/hogli up -d -y'仅在需要理解特定路由依赖时使用、和;自定义生成的配置不是默认的QA启动路径。
hogli dev:intentshogli dev:explain <intent>hogli dev:list-units <intent>首次启动需要60-90秒,用于Django导入和Vite预热。使用停止(保留Docker服务运行以实现快速重启)。
hogli down -yIs the stack ready?
栈是否已就绪?
There are two readiness bars. Don't conflate them.
Ready for — the app is reachable and you can drive it:
/runbash
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 reachableFor UI QA, and are hard gates; add only units the route needs, such as , , , , or . Stop here. Do not chase crashed units when only was asked — the stack is usable for launch, screenshot, and most UI scenes (home, login, settings, feature flags) regardless of migration state.
backendfrontendmcpfeature-flagsnodejscaptureingestionmigrate-*/runReady for of HogQL-backed scenes (insights, dashboards, web analytics) and for :
/verifyPOST /api/setup_test/...- Also requires to show
mcp__phrocs__get_process_status process="migrate-clickhouse". If it'sstatus:"done" exit_code:0, see the gotcha below.crashed
phrocs MCP tools — for either bar:
- (no args) — all units, with
mcp__phrocs__get_process_status,ready, memory, CPU.exit_code - — single unit.
mcp__phrocs__get_process_status process="frontend" - — tail recent stdout/stderr.
mcp__phrocs__get_process_logs process="backend" - — 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
mcp__phrocs__toggle_process process="<unit>"for a full clean restart.phrocs stop && hogli up -d
有两个就绪标准,请勿混淆。
/runbash
curl -sf http://localhost:8010/_health # 200
curl -sf -o /dev/null -w '%{http_code}' http://localhost:8010/ # 200 或 302
curl -sS http://localhost:8010/api/projects/@current | grep -q '"code":"not_authenticated"' # API + 数据库可达对于UI QA,和是硬性门槛;仅添加路由所需的单元,例如、、、或。到此为止。当仅要求执行时,无需排查崩溃的单元——无论迁移状态如何,该栈都可用于启动、截图和大多数UI场景(首页、登录、设置、功能开关)。
backendfrontendmcpfeature-flagsnodejscaptureingestion/runmigrate-*适用于HogQL支撑场景(洞察、仪表盘、网页分析)和的就绪标准:
POST /api/setup_test/.../verify- 还要求返回
mcp__phrocs__get_process_status process="migrate-clickhouse"。如果状态为status:"done" exit_code:0,请参阅下方的避坑指南。crashed
phrocs MCP工具——适用于两种就绪标准:
- (无参数)——查看所有单元,包含
mcp__phrocs__get_process_status、ready、内存、CPU信息。exit_code - ——查看单个单元。
mcp__phrocs__get_process_status process="frontend" - ——查看最近的标准输出/错误日志尾部。
mcp__phrocs__get_process_logs process="backend" - ——重启单个单元。自动模式下会在共享栈上阻止此操作(被视为重启共享基础设施)。如果被阻止,请请求用户批准,或运行
mcp__phrocs__toggle_process process="<unit>"进行完整的干净重启。phrocs stop && hogli up -d
Drive the UI for /verify
为/verify驱动UI
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: . Gated on , all of which local dev satisfies via . Implementation: + — 3 clusters via , ~5-10s end to end. Reference call site: .
POST /api/setup_test/organization_with_team/DEBUG=True | E2E_TESTING | CI | TESTDEBUG=Trueposthog/api/playwright_setup.pyposthog/test/playwright_setup_functions.py:create_organization_with_teamHedgeboxMatrixplaywright/utils/playwright-setup.ts:251Avoid for /run and /verify — it calls with (default at ) 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.
hogli dev:demo-datagenerate_demo_datan_clusters=500posthog/management/commands/generate_demo_data.py:59The full browser-MCP recipe:
-
new_page.http://localhost:8010/login -
the workspace bootstrap. Per-call email so reruns don't collide; password fixed at
evaluate_script; response gives back the team_id and a personal API key.12345678jsconst 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 result -
the in-page login. Runs in the page context so Django's CSRF middleware sees the right cookies (
evaluate_script).playwright/utils/playwright-setup.ts:282-291jsconst 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 user -
to
navigate_page.http://localhost:8010/project/{team_id}{scene-path} -
text or a
wait_forelement. PostHog attaches[data-attr]to virtually every rendered element; use it as the "page hydrated" signal.data-attrandnetworkidlenever settle because PostHog.js and Vite HMR keep polling.load -
. The accessibility snapshot from
take_screenshotis also enough on its own for most "is this element present?" verifications — no pixel comparison needed.wait_for
For API-only calls (creating fixtures, hitting endpoints without a browser), use the from the setup_test response as — no login dance.
personal_api_keyAuthorization: Bearer <key>For deeper interaction (clicking, filling forms, inspecting console), the same MCP toolkit ( / ) covers it. No project-specific wrapper needed.
chrome-devtools-mcp:*playwright:*由于没有事件数据,空的PostHog场景看起来会像损坏了一样。要验证UI变更,你需要一个包含真实数据的工作空间。标准方法与Playwright测试套件使用的相同:。该接口受条件限制,本地开发环境通过满足所有条件。实现位置: + ——通过创建3个集群,端到端耗时约5-10秒。参考调用点:。
POST /api/setup_test/organization_with_team/DEBUG=True | E2E_TESTING | CI | TESTDEBUG=Trueposthog/api/playwright_setup.pyposthog/test/playwright_setup_functions.py:create_organization_with_teamHedgeboxMatrixplaywright/utils/playwright-setup.ts:251在/run和/verify中避免使用——它会调用并设置(默认值见),耗时5-30分钟。该命令是为想要体验大型真实数据集的人员设计的;不适合自动化启动和截图场景。
hogli dev:demo-datagenerate_demo_datan_clusters=500posthog/management/commands/generate_demo_data.py:59完整的browser-MCP操作流程:
-
使用访问
new_page。http://localhost:8010/login -
使用执行工作空间初始化。每次调用使用不同的邮箱以避免重复运行冲突;密码固定为
evaluate_script;响应会返回team_id和个人API密钥。12345678jsconst 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 result -
使用执行页内登录。在页面上下文中运行,以便Django的CSRF中间件能识别正确的Cookie(
evaluate_script)。playwright/utils/playwright-setup.ts:282-291jsconst 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 = 已登录;403 = 不在页内上下文;400 = 工作空间未成功创建用户 -
使用跳转到
navigate_page。http://localhost:8010/project/{team_id}{scene-path} -
使用等待文本或
wait_for元素出现。PostHog几乎为每个渲染的元素都添加了[data-attr];可将其作为“页面已hydration”的信号。data-attr和networkidle事件永远不会稳定,因为PostHog.js和Vite HMR会持续轮询。load -
执行截图。对于大多数“该元素是否存在?”的验证,
take_screenshot返回的可访问性快照本身就足够了——无需像素对比。wait_for
对于纯API调用(创建测试夹具、无需浏览器访问端点),使用setup_test响应中的作为——无需登录流程。
personal_api_keyAuthorization: Bearer <key>对于更深度的交互(点击、填写表单、检查控制台),相同的MCP工具包( / )即可覆盖。无需项目特定的封装。
chrome-devtools-mcp:*playwright:*For /verify: diff → URL mapping
/verify指南:diff到URL的映射
The frontend uses kea-router. The mapping rule:
| Edited path | Scene URL (under |
|---|---|
| usually |
| |
| |
| |
| |
| per the |
| renders everywhere; verify on any scene |
| grep usages, screenshot the scene that mounts it |
Canonical sources to grep when the path isn't obvious:
- — top-level URL helpers (
frontend/src/scenes/urls.ts,urls.insights(), etc.).urls.dashboard(...) - — scene-to-route registry.
frontend/src/scenes/scenes.ts - — per-product routes.
products/<name>/manifest.tsx
前端使用kea-router。映射规则如下:
| 编辑路径 | 场景URL(位于 |
|---|---|
| 通常为 |
| |
| |
| |
| |
| 参见 |
| 全局渲染;可在任意场景验证 |
| grep使用位置,对挂载该组件的场景截图 |
当路径不明确时,可在以下标准文件中grep查找:
- ——顶层URL辅助函数(
frontend/src/scenes/urls.ts、urls.insights()等)。urls.dashboard(...) - ——场景到路由的注册表。
frontend/src/scenes/scenes.ts - ——各产品的路由。
products/<name>/manifest.tsx
Gotchas
避坑指南
- and
migrate-clickhouseoften crash on a coldmigrate-persons-dbdue to a startup race. They start in parallel withhogli up -d, and if Postgres isn't ready yet they crash. This is the hard prereq formigrate-postgresand for HogQL-backed scenes (insights, dashboards, web analytics) — but not forPOST /api/setup_test/.... Don't fix it unless the task needs HogQL//run. When you do need to fix it, the canonical sequence is: wait forsetup_testto showmigrate-postgresviastatus:"done", then restart the crashed migrations.mcp__phrocs__get_process_statusis the surgical tool but auto-mode blocks it on shared stacks — fall back tomcp__phrocs__toggle_processwhich re-runs everything in order, or runphrocs stop && hogli up -ddirectly (you'll needpython manage.py migrate_clickhousefirst soset -a; source .env.services; set +a, otherwise it targetsCLICKHOUSE_DATABASE=posthog). If neither works (corrupted CH replica state in ZooKeeper from a partial run),defaultis the only path — it wipes Docker volumes, destructive.hogli dev:reset - is a coarse all-process signal. Confirm with process-specific phrocs status, the
hogli waitprobes, and the target route before deciding the stack is unusable.curl - Vite serves on , not the URL you browse. You browse
:8234(the Envoy-style proxy). The proxy reverse-proxies Vite forhttp://localhost:8010and Django for everything else. Hitting/static/*directly returns 404 because Vite has no index route at the dev-server root.:8234/ - Worktrees share Docker containers but compete for ports. All worktrees on the same machine resolve to the same /
posthog-clickhouse-1containers, 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 beforeposthog-db-1here.hogli up -d - CSP warnings and 401s in the browser console are normal pre-auth. The preflight/login page tries to fetch ,
/api/projects/@current, and PostHog.js remote config — all 401 until you sign up. WASM/CSP "Report Only" warnings come from the dev CSP./api/users/@me/ - Direct returns 403 (CSRF). Session login must run in-page via
curl -X POST /api/login/so cookies and CSRF tokens flow. For non-browser API calls, use thebrowser_evaluatefrom thepersonal_api_keyresponse assetup_test— no CSRF on token auth.Authorization: Bearer <key> - may use 1Password refs. Without
.env.localinstalled, refs become literal strings (e.g.op) and downstream services fail with cryptic auth errors. InstallOPENAI_API_KEY=op://...or replace with literals.op
- 冷启动时,
hogli up -d和migrate-clickhouse经常因启动竞争而崩溃。 它们与migrate-persons-db并行启动,如果Postgres尚未就绪就会崩溃。这是migrate-postgres和HogQL支撑场景(洞察、仪表盘、网页分析)的硬性前置条件——但不是POST /api/setup_test/...的前置条件。除非任务需要HogQL//run,否则无需修复。当确实需要修复时,标准流程是:通过setup_test等待mcp__phrocs__get_process_status显示migrate-postgres,然后重启崩溃的迁移任务。status:"done"是精准工具,但自动模式下会在共享栈上阻止此操作——可回退到mcp__phrocs__toggle_process按顺序重新运行所有内容,或直接运行phrocs stop && hogli up -d(需要先执行python manage.py migrate_clickhouse以设置set -a; source .env.services; set +a,否则会指向CLICKHOUSE_DATABASE=posthog数据库)。如果两种方法都无效(由于部分运行导致ZooKeeper中的CH副本状态损坏),default是唯一的解决办法——它会清除Docker卷,具有破坏性。hogli dev:reset - 是一个粗粒度的全进程信号。 在判定栈不可用之前,请结合特定进程的phrocs状态、
hogli wait探测结果和目标路由进行确认。curl - Vite在端口提供服务,而不是你浏览的URL。 你访问的是
:8234(Envoy风格代理)。代理将http://localhost:8010的请求反向代理到Vite,其他所有请求反向代理到Django。直接访问/static/*会返回404,因为Vite在开发服务器根目录没有索引路由。:8234/ - 工作树共享Docker容器但会竞争端口。 同一机器上的所有工作树都会解析到相同的/
posthog-clickhouse-1容器,因此数据库状态是全局的。但8000/8010/8234端口同一时间只能被一个工作树占用——在此处执行posthog-db-1之前,请先终止其他工作树的granian/vite/phrocs进程。hogli up -d - 身份验证前,浏览器控制台中的CSP警告和401错误是正常的。 预检查/登录页面会尝试获取、
/api/projects/@current和PostHog.js远程配置——在你注册之前,这些请求都会返回401。WASM/CSP的“仅报告”警告来自开发环境的CSP配置。/api/users/@me/ - 直接执行会返回403(CSRF错误)。 会话登录必须通过
curl -X POST /api/login/在页内执行,以便Cookie和CSRF令牌正常传递。对于非浏览器API调用,使用browser_evaluate响应中的setup_test作为personal_api_key——令牌认证不存在CSRF问题。Authorization: Bearer <key> - 可能使用1Password引用。 如果未安装
.env.local,引用会变成字面字符串(例如op),导致下游服务出现难以理解的认证错误。请安装OPENAI_API_KEY=op://...或替换为字面量值。op
Troubleshooting
故障排查
- exits with
hogli up -d— previous run still active or crashed without cleanup.Another instance of bin/start is already runningshows what's there; if nothing's running,mcp__phrocs__get_process_statusand retry.rm bin/start.lock - fails with
docker info— OrbStack is stopped.dial unix /Users/<you>/.orbstack/run/docker.sock: ... no such file.open -a OrbStack - returns 500 instead of 401 — Postgres or ClickHouse unreachable.
/api/projects/@currentand look for unhealthy containers;docker ps | grep posthog-waits for all of them.hogli services:ready - returns 400
POST /api/login/— you're logging in as a user theinvalid_credentialsworkspace didn't actually create (CH crash usually). Check the response of the setup_test call; if it 500'd, fix CH first (see thesetup_testgotcha).migrate-clickhouse - returns 404 —
POST /api/setup_test/organization_with_team/,DEBUG,E2E_TESTING, andCIare all false. Local dev hasTESTby default; if it's not set,DEBUG=Trueis missing or.env.localpoints at a prod-like settings module.DJANGO_SETTINGS_MODULE - returns 500
POST /api/setup_test/organization_with_team/—Table posthog.person does not existcrashed during boot. See the gotcha above.migrate-clickhouse - In-page returns 403 — you're calling it from outside the page context (e.g.
fetch('/api/login/')rather thanpage.request.post). Usepage.evaluate/evaluate_scriptso the call originates in-page.browser_evaluate
- 退出并提示
hogli up -d—— 之前的运行仍在活动中,或崩溃后未清理。Another instance of bin/start is already running可显示当前运行的内容;如果没有任何进程在运行,执行mcp__phrocs__get_process_status后重试。rm bin/start.lock - 失败并提示
docker info—— OrbStack已停止。执行dial unix /Users/<you>/.orbstack/run/docker.sock: ... no such file启动。open -a OrbStack - 返回500而不是401 —— Postgres或ClickHouse不可达。执行
/api/projects/@current查看是否有不健康的容器;docker ps | grep posthog-会等待所有容器就绪。hogli services:ready - 返回400
POST /api/login/—— 你尝试登录的用户实际上并未被invalid_credentials工作空间创建(通常是因为CH崩溃)。检查setup_test调用的响应;如果返回500,请先修复CH(参见setup_test避坑指南)。migrate-clickhouse - 返回404 ——
POST /api/setup_test/organization_with_team/、DEBUG、E2E_TESTING和CI均为false。本地开发环境默认TEST;如果未设置,可能是DEBUG=True缺失,或.env.local指向了类生产环境的配置模块。DJANGO_SETTINGS_MODULE - 返回500
POST /api/setup_test/organization_with_team/—— 启动过程中Table posthog.person does not exist崩溃。参见上方的避坑指南。migrate-clickhouse - 页内返回403 —— 你是在页面上下文之外调用的(例如使用
fetch('/api/login/')而不是page.request.post)。请使用page.evaluate/evaluate_script,确保调用源自页内。browser_evaluate