[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-posthog-turning-engineering-analytics-into-insights":3,"mdc-gz3ez7-key":54,"related-repo-posthog-turning-engineering-analytics-into-insights":1469,"related-org-posthog-turning-engineering-analytics-into-insights":1570},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":29,"repoUrl":30,"updatedAt":31,"license":32,"forks":33,"topics":34,"repo":49,"sourceUrl":52,"mdContent":53},"turning-engineering-analytics-into-insights","convert engineering analytics into PostHog insights","Converts engineering analytics (PR \u002F CI) data into saved PostHog insights, dashboards, and subscriptions, and explains what data the product reads so it can be queried directly with SQL. The engineering analytics dashboard and MCP tools run curated HogQL privately over per-team GitHub warehouse tables; this skill teaches discovering those tables via engineering-analytics-sources, replicating the curated column semantics in HogQL, reading the exposed engineering_analytics_* warehouse views where product logic is involved (CI cost, fingerprinted failure lines, commit attribution), saving the query with insight-create, and scheduling delivery with subscriptions-create. Use when asked to \"save this as an insight\", \"put CI health \u002F merge times on a dashboard\", \"email me PR throughput weekly\", \"chart CI cost\", \"track time to first review\", \"subscribe to these numbers\", \"alert on CI success rate\", or \"what data\u002Ftables\u002Fviews does engineering analytics read\". For ad-hoc CI and merge questions use diagnosing-ci-and-merge-bottlenecks; to investigate one specific CI failure use investigating-ci-failures.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"posthog","PostHog","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fposthog.png",[12,16,19,20,23,26],{"name":13,"slug":14,"type":15},"Observability","observability","tag",{"name":17,"slug":18,"type":15},"Dashboards","dashboards",{"name":9,"slug":8,"type":15},{"name":21,"slug":22,"type":15},"Engineering","engineering",{"name":24,"slug":25,"type":15},"Analytics","analytics",{"name":27,"slug":28,"type":15},"SQL","sql",35568,"https:\u002F\u002Fgithub.com\u002FPostHog\u002Fposthog","2026-07-18T05:10:57.445014",null,2977,[35,36,25,37,38,39,40,41,42,43,44,45,46,47,48],"ab-testing","ai-analytics","cdp","data-warehouse","experiments","feature-flags","javascript","product-analytics","python","react","session-replay","surveys","typescript","web-analytics",{"repoUrl":30,"stars":29,"forks":33,"topics":50,"description":51},[35,36,25,37,38,39,40,41,42,43,44,45,46,47,48],"🦔 PostHog is an all-in-one developer platform for building successful products. We offer product analytics, web analytics, session replay, error tracking, feature flags, experimentation, surveys, data warehouse, a CDP, and an AI product assistant to help debug your code, ship features faster, and keep all your usage and customer data in one stack.","https:\u002F\u002Fgithub.com\u002FPostHog\u002Fposthog\u002Ftree\u002FHEAD\u002Fproducts\u002Fengineering_analytics\u002Fskills\u002Fturning-engineering-analytics-into-insights","---\nname: turning-engineering-analytics-into-insights\ndescription: >\n  Converts engineering analytics (PR \u002F CI) data into saved PostHog insights, dashboards, and subscriptions, and\n  explains what data the product reads so it can be queried directly with SQL. The engineering analytics dashboard\n  and MCP tools run curated HogQL privately over per-team GitHub warehouse tables; this skill teaches discovering\n  those tables via engineering-analytics-sources, replicating the curated column semantics in HogQL, reading the\n  exposed engineering_analytics_* warehouse views where product logic is involved (CI cost, fingerprinted failure\n  lines, commit attribution), saving the query with insight-create, and scheduling delivery with\n  subscriptions-create. Use when asked to \"save this as an insight\", \"put CI health \u002F merge times on a dashboard\",\n  \"email me PR throughput weekly\", \"chart CI cost\", \"track time to first review\", \"subscribe to these numbers\",\n  \"alert on CI success rate\", or \"what data\u002Ftables\u002Fviews does engineering analytics read\". For ad-hoc CI and merge\n  questions use diagnosing-ci-and-merge-bottlenecks; to investigate one specific CI failure use\n  investigating-ci-failures.\n---\n\n# Turning engineering analytics into insights and subscriptions\n\nThe engineering analytics dashboard and MCP tools (`pull-requests`, `workflow-health`, `pr-lifecycle`, `engineering-analytics-broken-tests`, …)\nrun curated HogQL privately: nothing in the UI or the tool output names the underlying tables,\nand the endpoints cannot themselves be saved as insights or subscribed to.\nThe data, however, is queryable directly, through two substrates:\n\n- **Raw warehouse tables** — `\u003Cprefix>github_pull_requests`, `\u003Cprefix>github_workflow_runs`,\n  `\u003Cprefix>github_workflow_jobs`, `\u003Cprefix>github_reviews`, and `\u003Cprefix>github_teams` \u002F\n  `\u003Cprefix>github_team_members` (org team membership — the author→team map) — ordinary team-scoped tables you query with HogQL.\n- **Three curated warehouse views** with fixed names — `engineering_analytics_job_costs`,\n  `engineering_analytics_ci_job_history`, `engineering_analytics_ci_failures` — provisioned per team from the\n  connected GitHub source(s).\n  Non-materialized: computed at query time, always current, and they back insights and subscriptions like any table.\n\nThe views exist for exactly one reason: they render **product code** into SQL — the runner-tier cost model, the\nfailure-fingerprint recipe, the jobs↔runs commit-attribution rules — logic that would silently drift if hand-rolled,\nre-rendered into the team's view whenever the code changes. Everything else is just table data, and **pure HogQL over\nthe raw tables is always enough**: never create additional warehouse views for engineering analytics data, and never\nre-derive in SQL what the three views already encode.\n\n| What the product shows                                      | Where the data actually lives                                                        | Can it back an insight?                        |\n| ----------------------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------- |\n| PR list, merge times, CI status, workflow health            | Data warehouse tables `\u003Cprefix>github_pull_requests`, `\u003Cprefix>github_workflow_runs` | **Yes** (SQL insight over the tables)          |\n| Reviews and approvals                                       | `\u003Cprefix>github_reviews`                                                             | **Yes**                                        |\n| Team-level PR metrics (author→team attribution)             | `\u003Cprefix>github_team_members` semi-joined against the PR authors                     | **Yes** (team aggregates only)                 |\n| Job durations, queue times, runner tiers                    | `\u003Cprefix>github_workflow_jobs`                                                       | **Yes**                                        |\n| CI cost (runner-tier price ladder)                          | `engineering_analytics_job_costs` view                                               | **Yes** (query the view, never recompute cost) |\n| Per-job CI history with commit attribution                  | `engineering_analytics_ci_job_history` view                                          | **Yes**                                        |\n| Grouped (fingerprinted) CI failure lines                    | `engineering_analytics_ci_failures` view (reads the Logs product, short retention)   | **Yes**, for short recent windows              |\n| Thinned CI failure logs for a PR or run                     | Logs product (`service_name = 'github-ci-logs'`) + thinning logic                    | No (use the MCP tools ad hoc)                  |\n| Flaky-test leaderboard, broken-tests triage, team CI health | CI trace spans + ranking\u002Fclassification logic in product code                        | No (use the MCP tools ad hoc)                  |\n\nSo the job splits cleanly:\nwarehouse-backed metrics (raw tables or the three views) become SQL insights (then dashboards, then subscriptions);\neverything computed by product logic at request time stays on the MCP tools, delivered recurringly via an AI subscription if needed.\n\n## Step 1: discover the team's tables\n\nWarehouse table names carry a user-chosen prefix, so never hardcode them.\nCall the `engineering-analytics-sources` MCP tool: each connected GitHub source returns its `id`, `repo`, and `prefix`.\nThe tables are `\u003Cprefix>github_pull_requests`, `\u003Cprefix>github_workflow_runs`, `\u003Cprefix>github_workflow_jobs`, `\u003Cprefix>github_reviews`, and `\u003Cprefix>github_teams` \u002F `\u003Cprefix>github_team_members`;\nan empty prefix means the plain `github_*` names.\nWith multiple sources, ask which repo the user means; each source is one repo.\n\nThe three `engineering_analytics_*` views need no discovery: their names are fixed (no prefix), and they cover all of\nthe team's GitHub sources at once — filter on `repo_owner` \u002F `repo_name` (`job_costs`, `ci_job_history`) or `repo`\n(`ci_failures`, which reads the Logs product rather than the per-source tables) to scope down to one repo.\n\n## Step 2: write HogQL that carries the curated semantics\n\nFirst check whether one of the three views already answers the question — cost, per-job history and commit\nattribution, fingerprinted failure lines — and query it directly; the rules below are for the raw tables.\n\nThe raw tables land GitHub's JSON verbatim, so a naive `SELECT` gets the domain rules wrong.\nCopy the base subqueries from [references\u002Fhogql-recipes.md](references\u002Fhogql-recipes.md) (they mirror the product's own curated builders) and follow these rules:\n\n- **Timestamps are strings.** Always `parseDateTimeBestEffort(created_at)` etc. before comparing or diffing.\n- **Nested JSON is Nullable.** `ifNull(...)`-unwrap before `JSONExtractArrayRaw` \u002F `splitByChar`: ClickHouse rejects an Array inside a Nullable.\n- **CI ↔ PR attribution is by PR number** (the run's `pull_requests` association), never by head SHA: the PR snapshot keeps only the current head, so a SHA join silently drops every push but the latest. head SHA is only for a PR's _current_ CI status (latest run per `(head_sha, workflow_name)`).\n- **Bot detection**: `author_handle LIKE '%[bot]' OR author_handle IN ('dependabot', 'github-actions', 'posthog-bot', 'renovate')`. Exclude bots and drafts from throughput \u002F merge-time metrics by default.\n- **Honest names.** `merged_at - created_at` is `open_to_merge_seconds` (it fuses draft and review time); never label an insight \"cycle time\" or \"review time\".\n- **Conclusions can be stale.** The runs sync watermarks on `created_at`; a run that completes late can show a stale conclusion. Compute rates over `status = 'completed'` rows only.\n- **Reviews join by `pr_number`.** In `github_reviews`, `state` is `APPROVED` \u002F `CHANGES_REQUESTED` \u002F `COMMENTED` \u002F `DISMISSED` (pending drafts are dropped at sync), and the injected `pr_number` joins to the PR table's `number`.\n- **Author→team attribution is a membership semi-join.** Filter `author_handle IN (SELECT member_handle FROM …)` against `github_team_members` (see the team recipe) — the shape the product's own team merge trend uses. A person can belong to several GitHub teams, so a plain JOIN would double-count their PRs across teams; and only team-level aggregates leave the query, never per-member figures.\n\nTest the query with the `execute-sql` MCP tool (or the SQL editor) before saving anything.\n\n## Step 3: save it as an insight\n\nUse `insight-create` with a SQL insight:\n\n```json\n{\n  \"name\": \"Weekly PR open→merge time (hours)\",\n  \"query\": {\n    \"kind\": \"DataVisualizationNode\",\n    \"source\": { \"kind\": \"HogQLQuery\", \"query\": \"\u003Ctested SQL>\" }\n  }\n}\n```\n\n`display` (a `ChartDisplayType`, e.g. `ActionsLineGraph`) and `chartSettings` (`xAxis` \u002F `yAxis` columns) turn the table into a chart;\nwhen unsure, save it and let the user pick the visualization in the UI, linking the returned insight URL.\nBake a relative window into the SQL (`>= now() - INTERVAL 90 DAY`);\nnote that a hard-coded date filter means dashboard date overrides won't apply to this tile.\nBundle several insights with `dashboard-create`.\n\n## Step 4: subscribe\n\nWith the insight (or dashboard) saved, this is standard subscription territory: follow the `managing-subscriptions` skill for the `subscriptions-create` payload, channels, frequency, and AI-summary options.\n\nFor \"notify me when X\" (a condition, not a schedule): alerts require a trends insight, so a SQL insight can't be alerted today.\nOffer a scheduled subscription instead, or a threshold check inside a prompt-kind AI subscription.\n\n## What NOT to rebuild in SQL\n\nThe **flaky-test ranking**, **broken-tests classification**, **team CI health rollup**, and **failure-log thinning** are product logic over data an insight can't reach (CI trace spans, raw log bodies);\nhand-rolled SQL versions will silently drift from what the dashboard shows.\nFor a recurring report on those, create a prompt-kind AI subscription (see the `creating-ai-subscription` skill) whose prompt asks for the relevant engineering analytics reading each period,\nor just call the MCP tools (`engineering-analytics-flaky-tests`, `engineering-analytics-broken-tests`, `engineering-analytics-team-ci-health`, `engineering-analytics-ci-failure-logs`, `engineering-analytics-run-failure-logs`) ad hoc.\n\nCI **cost** is not on that list because its product logic is rendered into the\n`engineering_analytics_job_costs` view (parity-tested against the product's own model, re-rendered when the model\nchanges), so cost insights are plain SQL over the view — and the cost MCP tools (`engineering-analytics-pr-cost`,\n`engineering-analytics-workflow-runner-costs`) read that same rendered SELECT, so the numbers agree. Still: never\nrecompute dollar cost from runner labels yourself.\n\n## Caveats to carry into every insight\n\nName these in the insight description so future readers inherit them:\n`open_to_merge_seconds` is coarse (draft + review fused);\nCI conclusions can lag until the run's webhook settles;\n`estimated_cost_usd` NULL means non-billable or still running, never zero — disambiguate via `provider` vs `completed_at`;\n`ci_failures` is pytest-only and failure-only, so its counts are absolute signal, never rates;\nbots and drafts are excluded (or not: say which).\nAnd never build per-author leaderboards or cross-author rankings; per-developer surveillance is an explicit product non-goal.\nWhen a team-level split is wanted, group through the `github_team_members` membership semi-join instead.\n",{"data":55,"body":56},{"name":4,"description":6},{"type":57,"children":58},"root",[59,68,105,199,218,479,484,491,572,630,636,641,662,920,933,939,952,1198,1262,1268,1289,1294,1300,1373,1407,1413,1463],{"type":60,"tag":61,"props":62,"children":64},"element","h1",{"id":63},"turning-engineering-analytics-into-insights-and-subscriptions",[65],{"type":66,"value":67},"text","Turning engineering analytics into insights and subscriptions",{"type":60,"tag":69,"props":70,"children":71},"p",{},[72,74,81,83,89,90,96,97,103],{"type":66,"value":73},"The engineering analytics dashboard and MCP tools (",{"type":60,"tag":75,"props":76,"children":78},"code",{"className":77},[],[79],{"type":66,"value":80},"pull-requests",{"type":66,"value":82},", ",{"type":60,"tag":75,"props":84,"children":86},{"className":85},[],[87],{"type":66,"value":88},"workflow-health",{"type":66,"value":82},{"type":60,"tag":75,"props":91,"children":93},{"className":92},[],[94],{"type":66,"value":95},"pr-lifecycle",{"type":66,"value":82},{"type":60,"tag":75,"props":98,"children":100},{"className":99},[],[101],{"type":66,"value":102},"engineering-analytics-broken-tests",{"type":66,"value":104},", …)\nrun curated HogQL privately: nothing in the UI or the tool output names the underlying tables,\nand the endpoints cannot themselves be saved as insights or subscribed to.\nThe data, however, is queryable directly, through two substrates:",{"type":60,"tag":106,"props":107,"children":108},"ul",{},[109,167],{"type":60,"tag":110,"props":111,"children":112},"li",{},[113,119,121,127,128,134,136,142,143,149,151,157,159,165],{"type":60,"tag":114,"props":115,"children":116},"strong",{},[117],{"type":66,"value":118},"Raw warehouse tables",{"type":66,"value":120}," — ",{"type":60,"tag":75,"props":122,"children":124},{"className":123},[],[125],{"type":66,"value":126},"\u003Cprefix>github_pull_requests",{"type":66,"value":82},{"type":60,"tag":75,"props":129,"children":131},{"className":130},[],[132],{"type":66,"value":133},"\u003Cprefix>github_workflow_runs",{"type":66,"value":135},",\n",{"type":60,"tag":75,"props":137,"children":139},{"className":138},[],[140],{"type":66,"value":141},"\u003Cprefix>github_workflow_jobs",{"type":66,"value":82},{"type":60,"tag":75,"props":144,"children":146},{"className":145},[],[147],{"type":66,"value":148},"\u003Cprefix>github_reviews",{"type":66,"value":150},", and ",{"type":60,"tag":75,"props":152,"children":154},{"className":153},[],[155],{"type":66,"value":156},"\u003Cprefix>github_teams",{"type":66,"value":158}," \u002F\n",{"type":60,"tag":75,"props":160,"children":162},{"className":161},[],[163],{"type":66,"value":164},"\u003Cprefix>github_team_members",{"type":66,"value":166}," (org team membership — the author→team map) — ordinary team-scoped tables you query with HogQL.",{"type":60,"tag":110,"props":168,"children":169},{},[170,175,177,183,184,190,191,197],{"type":60,"tag":114,"props":171,"children":172},{},[173],{"type":66,"value":174},"Three curated warehouse views",{"type":66,"value":176}," with fixed names — ",{"type":60,"tag":75,"props":178,"children":180},{"className":179},[],[181],{"type":66,"value":182},"engineering_analytics_job_costs",{"type":66,"value":135},{"type":60,"tag":75,"props":185,"children":187},{"className":186},[],[188],{"type":66,"value":189},"engineering_analytics_ci_job_history",{"type":66,"value":82},{"type":60,"tag":75,"props":192,"children":194},{"className":193},[],[195],{"type":66,"value":196},"engineering_analytics_ci_failures",{"type":66,"value":198}," — provisioned per team from the\nconnected GitHub source(s).\nNon-materialized: computed at query time, always current, and they back insights and subscriptions like any table.",{"type":60,"tag":69,"props":200,"children":201},{},[202,204,209,211,216],{"type":66,"value":203},"The views exist for exactly one reason: they render ",{"type":60,"tag":114,"props":205,"children":206},{},[207],{"type":66,"value":208},"product code",{"type":66,"value":210}," into SQL — the runner-tier cost model, the\nfailure-fingerprint recipe, the jobs↔runs commit-attribution rules — logic that would silently drift if hand-rolled,\nre-rendered into the team's view whenever the code changes. Everything else is just table data, and ",{"type":60,"tag":114,"props":212,"children":213},{},[214],{"type":66,"value":215},"pure HogQL over\nthe raw tables is always enough",{"type":66,"value":217},": never create additional warehouse views for engineering analytics data, and never\nre-derive in SQL what the three views already encode.",{"type":60,"tag":219,"props":220,"children":221},"table",{},[222,246],{"type":60,"tag":223,"props":224,"children":225},"thead",{},[226],{"type":60,"tag":227,"props":228,"children":229},"tr",{},[230,236,241],{"type":60,"tag":231,"props":232,"children":233},"th",{},[234],{"type":66,"value":235},"What the product shows",{"type":60,"tag":231,"props":237,"children":238},{},[239],{"type":66,"value":240},"Where the data actually lives",{"type":60,"tag":231,"props":242,"children":243},{},[244],{"type":66,"value":245},"Can it back an insight?",{"type":60,"tag":247,"props":248,"children":249},"tbody",{},[250,285,308,335,358,385,409,436,462],{"type":60,"tag":227,"props":251,"children":252},{},[253,259,275],{"type":60,"tag":254,"props":255,"children":256},"td",{},[257],{"type":66,"value":258},"PR list, merge times, CI status, workflow health",{"type":60,"tag":254,"props":260,"children":261},{},[262,264,269,270],{"type":66,"value":263},"Data warehouse tables ",{"type":60,"tag":75,"props":265,"children":267},{"className":266},[],[268],{"type":66,"value":126},{"type":66,"value":82},{"type":60,"tag":75,"props":271,"children":273},{"className":272},[],[274],{"type":66,"value":133},{"type":60,"tag":254,"props":276,"children":277},{},[278,283],{"type":60,"tag":114,"props":279,"children":280},{},[281],{"type":66,"value":282},"Yes",{"type":66,"value":284}," (SQL insight over the tables)",{"type":60,"tag":227,"props":286,"children":287},{},[288,293,301],{"type":60,"tag":254,"props":289,"children":290},{},[291],{"type":66,"value":292},"Reviews and approvals",{"type":60,"tag":254,"props":294,"children":295},{},[296],{"type":60,"tag":75,"props":297,"children":299},{"className":298},[],[300],{"type":66,"value":148},{"type":60,"tag":254,"props":302,"children":303},{},[304],{"type":60,"tag":114,"props":305,"children":306},{},[307],{"type":66,"value":282},{"type":60,"tag":227,"props":309,"children":310},{},[311,316,326],{"type":60,"tag":254,"props":312,"children":313},{},[314],{"type":66,"value":315},"Team-level PR metrics (author→team attribution)",{"type":60,"tag":254,"props":317,"children":318},{},[319,324],{"type":60,"tag":75,"props":320,"children":322},{"className":321},[],[323],{"type":66,"value":164},{"type":66,"value":325}," semi-joined against the PR authors",{"type":60,"tag":254,"props":327,"children":328},{},[329,333],{"type":60,"tag":114,"props":330,"children":331},{},[332],{"type":66,"value":282},{"type":66,"value":334}," (team aggregates only)",{"type":60,"tag":227,"props":336,"children":337},{},[338,343,351],{"type":60,"tag":254,"props":339,"children":340},{},[341],{"type":66,"value":342},"Job durations, queue times, runner tiers",{"type":60,"tag":254,"props":344,"children":345},{},[346],{"type":60,"tag":75,"props":347,"children":349},{"className":348},[],[350],{"type":66,"value":141},{"type":60,"tag":254,"props":352,"children":353},{},[354],{"type":60,"tag":114,"props":355,"children":356},{},[357],{"type":66,"value":282},{"type":60,"tag":227,"props":359,"children":360},{},[361,366,376],{"type":60,"tag":254,"props":362,"children":363},{},[364],{"type":66,"value":365},"CI cost (runner-tier price ladder)",{"type":60,"tag":254,"props":367,"children":368},{},[369,374],{"type":60,"tag":75,"props":370,"children":372},{"className":371},[],[373],{"type":66,"value":182},{"type":66,"value":375}," view",{"type":60,"tag":254,"props":377,"children":378},{},[379,383],{"type":60,"tag":114,"props":380,"children":381},{},[382],{"type":66,"value":282},{"type":66,"value":384}," (query the view, never recompute cost)",{"type":60,"tag":227,"props":386,"children":387},{},[388,393,402],{"type":60,"tag":254,"props":389,"children":390},{},[391],{"type":66,"value":392},"Per-job CI history with commit attribution",{"type":60,"tag":254,"props":394,"children":395},{},[396,401],{"type":60,"tag":75,"props":397,"children":399},{"className":398},[],[400],{"type":66,"value":189},{"type":66,"value":375},{"type":60,"tag":254,"props":403,"children":404},{},[405],{"type":60,"tag":114,"props":406,"children":407},{},[408],{"type":66,"value":282},{"type":60,"tag":227,"props":410,"children":411},{},[412,417,427],{"type":60,"tag":254,"props":413,"children":414},{},[415],{"type":66,"value":416},"Grouped (fingerprinted) CI failure lines",{"type":60,"tag":254,"props":418,"children":419},{},[420,425],{"type":60,"tag":75,"props":421,"children":423},{"className":422},[],[424],{"type":66,"value":196},{"type":66,"value":426}," view (reads the Logs product, short retention)",{"type":60,"tag":254,"props":428,"children":429},{},[430,434],{"type":60,"tag":114,"props":431,"children":432},{},[433],{"type":66,"value":282},{"type":66,"value":435},", for short recent windows",{"type":60,"tag":227,"props":437,"children":438},{},[439,444,457],{"type":60,"tag":254,"props":440,"children":441},{},[442],{"type":66,"value":443},"Thinned CI failure logs for a PR or run",{"type":60,"tag":254,"props":445,"children":446},{},[447,449,455],{"type":66,"value":448},"Logs product (",{"type":60,"tag":75,"props":450,"children":452},{"className":451},[],[453],{"type":66,"value":454},"service_name = 'github-ci-logs'",{"type":66,"value":456},") + thinning logic",{"type":60,"tag":254,"props":458,"children":459},{},[460],{"type":66,"value":461},"No (use the MCP tools ad hoc)",{"type":60,"tag":227,"props":463,"children":464},{},[465,470,475],{"type":60,"tag":254,"props":466,"children":467},{},[468],{"type":66,"value":469},"Flaky-test leaderboard, broken-tests triage, team CI health",{"type":60,"tag":254,"props":471,"children":472},{},[473],{"type":66,"value":474},"CI trace spans + ranking\u002Fclassification logic in product code",{"type":60,"tag":254,"props":476,"children":477},{},[478],{"type":66,"value":461},{"type":60,"tag":69,"props":480,"children":481},{},[482],{"type":66,"value":483},"So the job splits cleanly:\nwarehouse-backed metrics (raw tables or the three views) become SQL insights (then dashboards, then subscriptions);\neverything computed by product logic at request time stays on the MCP tools, delivered recurringly via an AI subscription if needed.",{"type":60,"tag":485,"props":486,"children":488},"h2",{"id":487},"step-1-discover-the-teams-tables",[489],{"type":66,"value":490},"Step 1: discover the team's tables",{"type":60,"tag":69,"props":492,"children":493},{},[494,496,502,504,510,511,517,518,524,526,531,532,537,538,543,544,549,550,555,557,562,564,570],{"type":66,"value":495},"Warehouse table names carry a user-chosen prefix, so never hardcode them.\nCall the ",{"type":60,"tag":75,"props":497,"children":499},{"className":498},[],[500],{"type":66,"value":501},"engineering-analytics-sources",{"type":66,"value":503}," MCP tool: each connected GitHub source returns its ",{"type":60,"tag":75,"props":505,"children":507},{"className":506},[],[508],{"type":66,"value":509},"id",{"type":66,"value":82},{"type":60,"tag":75,"props":512,"children":514},{"className":513},[],[515],{"type":66,"value":516},"repo",{"type":66,"value":150},{"type":60,"tag":75,"props":519,"children":521},{"className":520},[],[522],{"type":66,"value":523},"prefix",{"type":66,"value":525},".\nThe tables are ",{"type":60,"tag":75,"props":527,"children":529},{"className":528},[],[530],{"type":66,"value":126},{"type":66,"value":82},{"type":60,"tag":75,"props":533,"children":535},{"className":534},[],[536],{"type":66,"value":133},{"type":66,"value":82},{"type":60,"tag":75,"props":539,"children":541},{"className":540},[],[542],{"type":66,"value":141},{"type":66,"value":82},{"type":60,"tag":75,"props":545,"children":547},{"className":546},[],[548],{"type":66,"value":148},{"type":66,"value":150},{"type":60,"tag":75,"props":551,"children":553},{"className":552},[],[554],{"type":66,"value":156},{"type":66,"value":556}," \u002F ",{"type":60,"tag":75,"props":558,"children":560},{"className":559},[],[561],{"type":66,"value":164},{"type":66,"value":563},";\nan empty prefix means the plain ",{"type":60,"tag":75,"props":565,"children":567},{"className":566},[],[568],{"type":66,"value":569},"github_*",{"type":66,"value":571}," names.\nWith multiple sources, ask which repo the user means; each source is one repo.",{"type":60,"tag":69,"props":573,"children":574},{},[575,577,583,585,591,592,598,600,606,607,613,615,620,622,628],{"type":66,"value":576},"The three ",{"type":60,"tag":75,"props":578,"children":580},{"className":579},[],[581],{"type":66,"value":582},"engineering_analytics_*",{"type":66,"value":584}," views need no discovery: their names are fixed (no prefix), and they cover all of\nthe team's GitHub sources at once — filter on ",{"type":60,"tag":75,"props":586,"children":588},{"className":587},[],[589],{"type":66,"value":590},"repo_owner",{"type":66,"value":556},{"type":60,"tag":75,"props":593,"children":595},{"className":594},[],[596],{"type":66,"value":597},"repo_name",{"type":66,"value":599}," (",{"type":60,"tag":75,"props":601,"children":603},{"className":602},[],[604],{"type":66,"value":605},"job_costs",{"type":66,"value":82},{"type":60,"tag":75,"props":608,"children":610},{"className":609},[],[611],{"type":66,"value":612},"ci_job_history",{"type":66,"value":614},") or ",{"type":60,"tag":75,"props":616,"children":618},{"className":617},[],[619],{"type":66,"value":516},{"type":66,"value":621},"\n(",{"type":60,"tag":75,"props":623,"children":625},{"className":624},[],[626],{"type":66,"value":627},"ci_failures",{"type":66,"value":629},", which reads the Logs product rather than the per-source tables) to scope down to one repo.",{"type":60,"tag":485,"props":631,"children":633},{"id":632},"step-2-write-hogql-that-carries-the-curated-semantics",[634],{"type":66,"value":635},"Step 2: write HogQL that carries the curated semantics",{"type":60,"tag":69,"props":637,"children":638},{},[639],{"type":66,"value":640},"First check whether one of the three views already answers the question — cost, per-job history and commit\nattribution, fingerprinted failure lines — and query it directly; the rules below are for the raw tables.",{"type":60,"tag":69,"props":642,"children":643},{},[644,646,652,654,660],{"type":66,"value":645},"The raw tables land GitHub's JSON verbatim, so a naive ",{"type":60,"tag":75,"props":647,"children":649},{"className":648},[],[650],{"type":66,"value":651},"SELECT",{"type":66,"value":653}," gets the domain rules wrong.\nCopy the base subqueries from ",{"type":60,"tag":655,"props":656,"children":658},"a",{"href":657},"references\u002Fhogql-recipes.md",[659],{"type":66,"value":657},{"type":66,"value":661}," (they mirror the product's own curated builders) and follow these rules:",{"type":60,"tag":106,"props":663,"children":664},{},[665,683,716,750,768,793,819,894],{"type":60,"tag":110,"props":666,"children":667},{},[668,673,675,681],{"type":60,"tag":114,"props":669,"children":670},{},[671],{"type":66,"value":672},"Timestamps are strings.",{"type":66,"value":674}," Always ",{"type":60,"tag":75,"props":676,"children":678},{"className":677},[],[679],{"type":66,"value":680},"parseDateTimeBestEffort(created_at)",{"type":66,"value":682}," etc. before comparing or diffing.",{"type":60,"tag":110,"props":684,"children":685},{},[686,691,693,699,701,707,708,714],{"type":60,"tag":114,"props":687,"children":688},{},[689],{"type":66,"value":690},"Nested JSON is Nullable.",{"type":66,"value":692}," ",{"type":60,"tag":75,"props":694,"children":696},{"className":695},[],[697],{"type":66,"value":698},"ifNull(...)",{"type":66,"value":700},"-unwrap before ",{"type":60,"tag":75,"props":702,"children":704},{"className":703},[],[705],{"type":66,"value":706},"JSONExtractArrayRaw",{"type":66,"value":556},{"type":60,"tag":75,"props":709,"children":711},{"className":710},[],[712],{"type":66,"value":713},"splitByChar",{"type":66,"value":715},": ClickHouse rejects an Array inside a Nullable.",{"type":60,"tag":110,"props":717,"children":718},{},[719,724,726,732,734,740,742,748],{"type":60,"tag":114,"props":720,"children":721},{},[722],{"type":66,"value":723},"CI ↔ PR attribution is by PR number",{"type":66,"value":725}," (the run's ",{"type":60,"tag":75,"props":727,"children":729},{"className":728},[],[730],{"type":66,"value":731},"pull_requests",{"type":66,"value":733}," association), never by head SHA: the PR snapshot keeps only the current head, so a SHA join silently drops every push but the latest. head SHA is only for a PR's ",{"type":60,"tag":735,"props":736,"children":737},"em",{},[738],{"type":66,"value":739},"current",{"type":66,"value":741}," CI status (latest run per ",{"type":60,"tag":75,"props":743,"children":745},{"className":744},[],[746],{"type":66,"value":747},"(head_sha, workflow_name)",{"type":66,"value":749},").",{"type":60,"tag":110,"props":751,"children":752},{},[753,758,760,766],{"type":60,"tag":114,"props":754,"children":755},{},[756],{"type":66,"value":757},"Bot detection",{"type":66,"value":759},": ",{"type":60,"tag":75,"props":761,"children":763},{"className":762},[],[764],{"type":66,"value":765},"author_handle LIKE '%[bot]' OR author_handle IN ('dependabot', 'github-actions', 'posthog-bot', 'renovate')",{"type":66,"value":767},". Exclude bots and drafts from throughput \u002F merge-time metrics by default.",{"type":60,"tag":110,"props":769,"children":770},{},[771,776,777,783,785,791],{"type":60,"tag":114,"props":772,"children":773},{},[774],{"type":66,"value":775},"Honest names.",{"type":66,"value":692},{"type":60,"tag":75,"props":778,"children":780},{"className":779},[],[781],{"type":66,"value":782},"merged_at - created_at",{"type":66,"value":784}," is ",{"type":60,"tag":75,"props":786,"children":788},{"className":787},[],[789],{"type":66,"value":790},"open_to_merge_seconds",{"type":66,"value":792}," (it fuses draft and review time); never label an insight \"cycle time\" or \"review time\".",{"type":60,"tag":110,"props":794,"children":795},{},[796,801,803,809,811,817],{"type":60,"tag":114,"props":797,"children":798},{},[799],{"type":66,"value":800},"Conclusions can be stale.",{"type":66,"value":802}," The runs sync watermarks on ",{"type":60,"tag":75,"props":804,"children":806},{"className":805},[],[807],{"type":66,"value":808},"created_at",{"type":66,"value":810},"; a run that completes late can show a stale conclusion. Compute rates over ",{"type":60,"tag":75,"props":812,"children":814},{"className":813},[],[815],{"type":66,"value":816},"status = 'completed'",{"type":66,"value":818}," rows only.",{"type":60,"tag":110,"props":820,"children":821},{},[822,835,837,843,844,850,851,857,858,864,865,871,872,878,880,885,887,893],{"type":60,"tag":114,"props":823,"children":824},{},[825,827,833],{"type":66,"value":826},"Reviews join by ",{"type":60,"tag":75,"props":828,"children":830},{"className":829},[],[831],{"type":66,"value":832},"pr_number",{"type":66,"value":834},".",{"type":66,"value":836}," In ",{"type":60,"tag":75,"props":838,"children":840},{"className":839},[],[841],{"type":66,"value":842},"github_reviews",{"type":66,"value":82},{"type":60,"tag":75,"props":845,"children":847},{"className":846},[],[848],{"type":66,"value":849},"state",{"type":66,"value":784},{"type":60,"tag":75,"props":852,"children":854},{"className":853},[],[855],{"type":66,"value":856},"APPROVED",{"type":66,"value":556},{"type":60,"tag":75,"props":859,"children":861},{"className":860},[],[862],{"type":66,"value":863},"CHANGES_REQUESTED",{"type":66,"value":556},{"type":60,"tag":75,"props":866,"children":868},{"className":867},[],[869],{"type":66,"value":870},"COMMENTED",{"type":66,"value":556},{"type":60,"tag":75,"props":873,"children":875},{"className":874},[],[876],{"type":66,"value":877},"DISMISSED",{"type":66,"value":879}," (pending drafts are dropped at sync), and the injected ",{"type":60,"tag":75,"props":881,"children":883},{"className":882},[],[884],{"type":66,"value":832},{"type":66,"value":886}," joins to the PR table's ",{"type":60,"tag":75,"props":888,"children":890},{"className":889},[],[891],{"type":66,"value":892},"number",{"type":66,"value":834},{"type":60,"tag":110,"props":895,"children":896},{},[897,902,904,910,912,918],{"type":60,"tag":114,"props":898,"children":899},{},[900],{"type":66,"value":901},"Author→team attribution is a membership semi-join.",{"type":66,"value":903}," Filter ",{"type":60,"tag":75,"props":905,"children":907},{"className":906},[],[908],{"type":66,"value":909},"author_handle IN (SELECT member_handle FROM …)",{"type":66,"value":911}," against ",{"type":60,"tag":75,"props":913,"children":915},{"className":914},[],[916],{"type":66,"value":917},"github_team_members",{"type":66,"value":919}," (see the team recipe) — the shape the product's own team merge trend uses. A person can belong to several GitHub teams, so a plain JOIN would double-count their PRs across teams; and only team-level aggregates leave the query, never per-member figures.",{"type":60,"tag":69,"props":921,"children":922},{},[923,925,931],{"type":66,"value":924},"Test the query with the ",{"type":60,"tag":75,"props":926,"children":928},{"className":927},[],[929],{"type":66,"value":930},"execute-sql",{"type":66,"value":932}," MCP tool (or the SQL editor) before saving anything.",{"type":60,"tag":485,"props":934,"children":936},{"id":935},"step-3-save-it-as-an-insight",[937],{"type":66,"value":938},"Step 3: save it as an insight",{"type":60,"tag":69,"props":940,"children":941},{},[942,944,950],{"type":66,"value":943},"Use ",{"type":60,"tag":75,"props":945,"children":947},{"className":946},[],[948],{"type":66,"value":949},"insight-create",{"type":66,"value":951}," with a SQL insight:",{"type":60,"tag":953,"props":954,"children":959},"pre",{"className":955,"code":956,"language":957,"meta":958,"style":958},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"name\": \"Weekly PR open→merge time (hours)\",\n  \"query\": {\n    \"kind\": \"DataVisualizationNode\",\n    \"source\": { \"kind\": \"HogQLQuery\", \"query\": \"\u003Ctested SQL>\" }\n  }\n}\n","json","",[960],{"type":60,"tag":75,"props":961,"children":962},{"__ignoreMap":958},[963,975,1019,1045,1085,1180,1189],{"type":60,"tag":964,"props":965,"children":968},"span",{"class":966,"line":967},"line",1,[969],{"type":60,"tag":964,"props":970,"children":972},{"style":971},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[973],{"type":66,"value":974},"{\n",{"type":60,"tag":964,"props":976,"children":978},{"class":966,"line":977},2,[979,984,990,995,1000,1005,1011,1015],{"type":60,"tag":964,"props":980,"children":981},{"style":971},[982],{"type":66,"value":983},"  \"",{"type":60,"tag":964,"props":985,"children":987},{"style":986},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[988],{"type":66,"value":989},"name",{"type":60,"tag":964,"props":991,"children":992},{"style":971},[993],{"type":66,"value":994},"\"",{"type":60,"tag":964,"props":996,"children":997},{"style":971},[998],{"type":66,"value":999},":",{"type":60,"tag":964,"props":1001,"children":1002},{"style":971},[1003],{"type":66,"value":1004}," \"",{"type":60,"tag":964,"props":1006,"children":1008},{"style":1007},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1009],{"type":66,"value":1010},"Weekly PR open→merge time (hours)",{"type":60,"tag":964,"props":1012,"children":1013},{"style":971},[1014],{"type":66,"value":994},{"type":60,"tag":964,"props":1016,"children":1017},{"style":971},[1018],{"type":66,"value":135},{"type":60,"tag":964,"props":1020,"children":1022},{"class":966,"line":1021},3,[1023,1027,1032,1036,1040],{"type":60,"tag":964,"props":1024,"children":1025},{"style":971},[1026],{"type":66,"value":983},{"type":60,"tag":964,"props":1028,"children":1029},{"style":986},[1030],{"type":66,"value":1031},"query",{"type":60,"tag":964,"props":1033,"children":1034},{"style":971},[1035],{"type":66,"value":994},{"type":60,"tag":964,"props":1037,"children":1038},{"style":971},[1039],{"type":66,"value":999},{"type":60,"tag":964,"props":1041,"children":1042},{"style":971},[1043],{"type":66,"value":1044}," {\n",{"type":60,"tag":964,"props":1046,"children":1048},{"class":966,"line":1047},4,[1049,1054,1060,1064,1068,1072,1077,1081],{"type":60,"tag":964,"props":1050,"children":1051},{"style":971},[1052],{"type":66,"value":1053},"    \"",{"type":60,"tag":964,"props":1055,"children":1057},{"style":1056},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[1058],{"type":66,"value":1059},"kind",{"type":60,"tag":964,"props":1061,"children":1062},{"style":971},[1063],{"type":66,"value":994},{"type":60,"tag":964,"props":1065,"children":1066},{"style":971},[1067],{"type":66,"value":999},{"type":60,"tag":964,"props":1069,"children":1070},{"style":971},[1071],{"type":66,"value":1004},{"type":60,"tag":964,"props":1073,"children":1074},{"style":1007},[1075],{"type":66,"value":1076},"DataVisualizationNode",{"type":60,"tag":964,"props":1078,"children":1079},{"style":971},[1080],{"type":66,"value":994},{"type":60,"tag":964,"props":1082,"children":1083},{"style":971},[1084],{"type":66,"value":135},{"type":60,"tag":964,"props":1086,"children":1088},{"class":966,"line":1087},5,[1089,1093,1098,1102,1106,1111,1115,1120,1124,1128,1132,1137,1141,1146,1150,1154,1158,1162,1166,1171,1175],{"type":60,"tag":964,"props":1090,"children":1091},{"style":971},[1092],{"type":66,"value":1053},{"type":60,"tag":964,"props":1094,"children":1095},{"style":1056},[1096],{"type":66,"value":1097},"source",{"type":60,"tag":964,"props":1099,"children":1100},{"style":971},[1101],{"type":66,"value":994},{"type":60,"tag":964,"props":1103,"children":1104},{"style":971},[1105],{"type":66,"value":999},{"type":60,"tag":964,"props":1107,"children":1108},{"style":971},[1109],{"type":66,"value":1110}," {",{"type":60,"tag":964,"props":1112,"children":1113},{"style":971},[1114],{"type":66,"value":1004},{"type":60,"tag":964,"props":1116,"children":1118},{"style":1117},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[1119],{"type":66,"value":1059},{"type":60,"tag":964,"props":1121,"children":1122},{"style":971},[1123],{"type":66,"value":994},{"type":60,"tag":964,"props":1125,"children":1126},{"style":971},[1127],{"type":66,"value":999},{"type":60,"tag":964,"props":1129,"children":1130},{"style":971},[1131],{"type":66,"value":1004},{"type":60,"tag":964,"props":1133,"children":1134},{"style":1007},[1135],{"type":66,"value":1136},"HogQLQuery",{"type":60,"tag":964,"props":1138,"children":1139},{"style":971},[1140],{"type":66,"value":994},{"type":60,"tag":964,"props":1142,"children":1143},{"style":971},[1144],{"type":66,"value":1145},",",{"type":60,"tag":964,"props":1147,"children":1148},{"style":971},[1149],{"type":66,"value":1004},{"type":60,"tag":964,"props":1151,"children":1152},{"style":1117},[1153],{"type":66,"value":1031},{"type":60,"tag":964,"props":1155,"children":1156},{"style":971},[1157],{"type":66,"value":994},{"type":60,"tag":964,"props":1159,"children":1160},{"style":971},[1161],{"type":66,"value":999},{"type":60,"tag":964,"props":1163,"children":1164},{"style":971},[1165],{"type":66,"value":1004},{"type":60,"tag":964,"props":1167,"children":1168},{"style":1007},[1169],{"type":66,"value":1170},"\u003Ctested SQL>",{"type":60,"tag":964,"props":1172,"children":1173},{"style":971},[1174],{"type":66,"value":994},{"type":60,"tag":964,"props":1176,"children":1177},{"style":971},[1178],{"type":66,"value":1179}," }\n",{"type":60,"tag":964,"props":1181,"children":1183},{"class":966,"line":1182},6,[1184],{"type":60,"tag":964,"props":1185,"children":1186},{"style":971},[1187],{"type":66,"value":1188},"  }\n",{"type":60,"tag":964,"props":1190,"children":1192},{"class":966,"line":1191},7,[1193],{"type":60,"tag":964,"props":1194,"children":1195},{"style":971},[1196],{"type":66,"value":1197},"}\n",{"type":60,"tag":69,"props":1199,"children":1200},{},[1201,1207,1209,1215,1217,1223,1225,1231,1232,1238,1239,1245,1247,1253,1255,1261],{"type":60,"tag":75,"props":1202,"children":1204},{"className":1203},[],[1205],{"type":66,"value":1206},"display",{"type":66,"value":1208}," (a ",{"type":60,"tag":75,"props":1210,"children":1212},{"className":1211},[],[1213],{"type":66,"value":1214},"ChartDisplayType",{"type":66,"value":1216},", e.g. ",{"type":60,"tag":75,"props":1218,"children":1220},{"className":1219},[],[1221],{"type":66,"value":1222},"ActionsLineGraph",{"type":66,"value":1224},") and ",{"type":60,"tag":75,"props":1226,"children":1228},{"className":1227},[],[1229],{"type":66,"value":1230},"chartSettings",{"type":66,"value":599},{"type":60,"tag":75,"props":1233,"children":1235},{"className":1234},[],[1236],{"type":66,"value":1237},"xAxis",{"type":66,"value":556},{"type":60,"tag":75,"props":1240,"children":1242},{"className":1241},[],[1243],{"type":66,"value":1244},"yAxis",{"type":66,"value":1246}," columns) turn the table into a chart;\nwhen unsure, save it and let the user pick the visualization in the UI, linking the returned insight URL.\nBake a relative window into the SQL (",{"type":60,"tag":75,"props":1248,"children":1250},{"className":1249},[],[1251],{"type":66,"value":1252},">= now() - INTERVAL 90 DAY",{"type":66,"value":1254},");\nnote that a hard-coded date filter means dashboard date overrides won't apply to this tile.\nBundle several insights with ",{"type":60,"tag":75,"props":1256,"children":1258},{"className":1257},[],[1259],{"type":66,"value":1260},"dashboard-create",{"type":66,"value":834},{"type":60,"tag":485,"props":1263,"children":1265},{"id":1264},"step-4-subscribe",[1266],{"type":66,"value":1267},"Step 4: subscribe",{"type":60,"tag":69,"props":1269,"children":1270},{},[1271,1273,1279,1281,1287],{"type":66,"value":1272},"With the insight (or dashboard) saved, this is standard subscription territory: follow the ",{"type":60,"tag":75,"props":1274,"children":1276},{"className":1275},[],[1277],{"type":66,"value":1278},"managing-subscriptions",{"type":66,"value":1280}," skill for the ",{"type":60,"tag":75,"props":1282,"children":1284},{"className":1283},[],[1285],{"type":66,"value":1286},"subscriptions-create",{"type":66,"value":1288}," payload, channels, frequency, and AI-summary options.",{"type":60,"tag":69,"props":1290,"children":1291},{},[1292],{"type":66,"value":1293},"For \"notify me when X\" (a condition, not a schedule): alerts require a trends insight, so a SQL insight can't be alerted today.\nOffer a scheduled subscription instead, or a threshold check inside a prompt-kind AI subscription.",{"type":60,"tag":485,"props":1295,"children":1297},{"id":1296},"what-not-to-rebuild-in-sql",[1298],{"type":66,"value":1299},"What NOT to rebuild in SQL",{"type":60,"tag":69,"props":1301,"children":1302},{},[1303,1305,1310,1311,1316,1317,1322,1323,1328,1330,1336,1338,1344,1345,1350,1351,1357,1358,1364,1365,1371],{"type":66,"value":1304},"The ",{"type":60,"tag":114,"props":1306,"children":1307},{},[1308],{"type":66,"value":1309},"flaky-test ranking",{"type":66,"value":82},{"type":60,"tag":114,"props":1312,"children":1313},{},[1314],{"type":66,"value":1315},"broken-tests classification",{"type":66,"value":82},{"type":60,"tag":114,"props":1318,"children":1319},{},[1320],{"type":66,"value":1321},"team CI health rollup",{"type":66,"value":150},{"type":60,"tag":114,"props":1324,"children":1325},{},[1326],{"type":66,"value":1327},"failure-log thinning",{"type":66,"value":1329}," are product logic over data an insight can't reach (CI trace spans, raw log bodies);\nhand-rolled SQL versions will silently drift from what the dashboard shows.\nFor a recurring report on those, create a prompt-kind AI subscription (see the ",{"type":60,"tag":75,"props":1331,"children":1333},{"className":1332},[],[1334],{"type":66,"value":1335},"creating-ai-subscription",{"type":66,"value":1337}," skill) whose prompt asks for the relevant engineering analytics reading each period,\nor just call the MCP tools (",{"type":60,"tag":75,"props":1339,"children":1341},{"className":1340},[],[1342],{"type":66,"value":1343},"engineering-analytics-flaky-tests",{"type":66,"value":82},{"type":60,"tag":75,"props":1346,"children":1348},{"className":1347},[],[1349],{"type":66,"value":102},{"type":66,"value":82},{"type":60,"tag":75,"props":1352,"children":1354},{"className":1353},[],[1355],{"type":66,"value":1356},"engineering-analytics-team-ci-health",{"type":66,"value":82},{"type":60,"tag":75,"props":1359,"children":1361},{"className":1360},[],[1362],{"type":66,"value":1363},"engineering-analytics-ci-failure-logs",{"type":66,"value":82},{"type":60,"tag":75,"props":1366,"children":1368},{"className":1367},[],[1369],{"type":66,"value":1370},"engineering-analytics-run-failure-logs",{"type":66,"value":1372},") ad hoc.",{"type":60,"tag":69,"props":1374,"children":1375},{},[1376,1378,1383,1385,1390,1392,1398,1399,1405],{"type":66,"value":1377},"CI ",{"type":60,"tag":114,"props":1379,"children":1380},{},[1381],{"type":66,"value":1382},"cost",{"type":66,"value":1384}," is not on that list because its product logic is rendered into the\n",{"type":60,"tag":75,"props":1386,"children":1388},{"className":1387},[],[1389],{"type":66,"value":182},{"type":66,"value":1391}," view (parity-tested against the product's own model, re-rendered when the model\nchanges), so cost insights are plain SQL over the view — and the cost MCP tools (",{"type":60,"tag":75,"props":1393,"children":1395},{"className":1394},[],[1396],{"type":66,"value":1397},"engineering-analytics-pr-cost",{"type":66,"value":135},{"type":60,"tag":75,"props":1400,"children":1402},{"className":1401},[],[1403],{"type":66,"value":1404},"engineering-analytics-workflow-runner-costs",{"type":66,"value":1406},") read that same rendered SELECT, so the numbers agree. Still: never\nrecompute dollar cost from runner labels yourself.",{"type":60,"tag":485,"props":1408,"children":1410},{"id":1409},"caveats-to-carry-into-every-insight",[1411],{"type":66,"value":1412},"Caveats to carry into every insight",{"type":60,"tag":69,"props":1414,"children":1415},{},[1416,1418,1423,1425,1431,1433,1439,1441,1447,1449,1454,1456,1461],{"type":66,"value":1417},"Name these in the insight description so future readers inherit them:\n",{"type":60,"tag":75,"props":1419,"children":1421},{"className":1420},[],[1422],{"type":66,"value":790},{"type":66,"value":1424}," is coarse (draft + review fused);\nCI conclusions can lag until the run's webhook settles;\n",{"type":60,"tag":75,"props":1426,"children":1428},{"className":1427},[],[1429],{"type":66,"value":1430},"estimated_cost_usd",{"type":66,"value":1432}," NULL means non-billable or still running, never zero — disambiguate via ",{"type":60,"tag":75,"props":1434,"children":1436},{"className":1435},[],[1437],{"type":66,"value":1438},"provider",{"type":66,"value":1440}," vs ",{"type":60,"tag":75,"props":1442,"children":1444},{"className":1443},[],[1445],{"type":66,"value":1446},"completed_at",{"type":66,"value":1448},";\n",{"type":60,"tag":75,"props":1450,"children":1452},{"className":1451},[],[1453],{"type":66,"value":627},{"type":66,"value":1455}," is pytest-only and failure-only, so its counts are absolute signal, never rates;\nbots and drafts are excluded (or not: say which).\nAnd never build per-author leaderboards or cross-author rankings; per-developer surveillance is an explicit product non-goal.\nWhen a team-level split is wanted, group through the ",{"type":60,"tag":75,"props":1457,"children":1459},{"className":1458},[],[1460],{"type":66,"value":917},{"type":66,"value":1462}," membership semi-join instead.",{"type":60,"tag":1464,"props":1465,"children":1466},"style",{},[1467],{"type":66,"value":1468},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":1470,"total":1569},[1471,1484,1496,1508,1521,1536,1552],{"slug":1472,"name":1472,"fn":1473,"description":1474,"org":1475,"tags":1476,"stars":29,"repoUrl":30,"updatedAt":1483},"analyzing-expensive-users","analyze expensive users in AI observability","Analyze the most expensive users in AI observability and explain why they cost so much. Use when the user asks about top spenders, expensive users, per-user LLM cost, user-level cost drivers, or patterns behind high AI observability spend.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1477,1478,1481,1482],{"name":24,"slug":25,"type":15},{"name":1479,"slug":1480,"type":15},"Cost Optimization","cost-optimization",{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},"2026-07-28T05:34:11.117757",{"slug":1485,"name":1485,"fn":1486,"description":1487,"org":1488,"tags":1489,"stars":29,"repoUrl":30,"updatedAt":1495},"auditing-endpoints","audit PostHog project endpoints","Audit every endpoint in a PostHog project for staleness, failed materialisations, and unused materialised versions. Use when the user asks \"what endpoints can I clean up?\", \"are any of my endpoints broken?\", \"which materialised versions are still being called?\", or wants a one-shot cleanup pass over the Endpoints product. Produces a prioritised report grouped by issue type, with recommended actions but does not modify anything without explicit confirmation.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1490,1491,1494],{"name":24,"slug":25,"type":15},{"name":1492,"slug":1493,"type":15},"Audit","audit",{"name":9,"slug":8,"type":15},"2026-06-08T08:08:33.693989",{"slug":1497,"name":1497,"fn":1498,"description":1499,"org":1500,"tags":1501,"stars":29,"repoUrl":30,"updatedAt":1507},"auditing-warehouse-source-health","audit PostHog data warehouse source health","Audit the health of a PostHog project's data warehouse sources and syncs — find every broken or degraded source connection, sync schema, and webhook channel. Use when the user asks \"why are my imports failing?\", \"what's broken with my sources?\", \"why is my warehouse data stale?\", or wants a one-shot triage of source\u002Fsync health before deciding where to dig in. Produces a prioritized report grouped by severity, with recommended next steps. For materialized-view health use `auditing-warehouse-view-health`; for a single failing sync use `diagnosing-failed-warehouse-syncs`.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1502,1503,1505,1506],{"name":1492,"slug":1493,"type":15},{"name":1504,"slug":38,"type":15},"Data Warehouse",{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:22:57.67984",{"slug":1509,"name":1509,"fn":1510,"description":1511,"org":1512,"tags":1513,"stars":29,"repoUrl":30,"updatedAt":1520},"auditing-warehouse-view-health","audit PostHog materialized view health","Audit the health of a PostHog project's materialized views (saved queries) — find every failed materialization and flag unused or stale materialized views that cost storage and compute. Use when the user asks \"which of my views are broken?\", \"why is this materialized view failing?\", \"are any of my views wasting compute?\", or wants a one-shot triage of view health. For source\u002Fsync health use `auditing-warehouse-source-health`.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1514,1515,1516,1519],{"name":1492,"slug":1493,"type":15},{"name":1504,"slug":38,"type":15},{"name":1517,"slug":1518,"type":15},"Performance","performance",{"name":9,"slug":8,"type":15},"2026-06-18T08:25:10.936787",{"slug":1522,"name":1522,"fn":1523,"description":1524,"org":1525,"tags":1526,"stars":29,"repoUrl":30,"updatedAt":1535},"authoring-error-tracking-alerts","author PostHog error tracking alerts","Author error tracking alerts that fire when an issue is created, reopened, or starts spiking. Use when the user asks to set up error notifications, route exceptions to Slack\u002Fwebhook\u002FLinear, or evaluate which error events are worth alerting on. Covers trigger-event selection, integration choice, dedup against existing alerts, and shipping with the canonical message body shape.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1527,1530,1533,1534],{"name":1528,"slug":1529,"type":15},"Alerting","alerting",{"name":1531,"slug":1532,"type":15},"Debugging","debugging",{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:24:40.318583",{"slug":1537,"name":1537,"fn":1538,"description":1539,"org":1540,"tags":1541,"stars":29,"repoUrl":30,"updatedAt":1551},"authoring-log-alerts","author log alerts in PostHog","Author useful, low-noise log alerts on services in a PostHog project. Use when the user asks to set up alerts for their logs, suggest alerts they should add, or evaluate whether a service is worth monitoring. Covers service triage, baseline characterisation, threshold drafting, back-testing via simulate, and shipping with a notification destination.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1542,1543,1546,1547,1550],{"name":24,"slug":25,"type":15},{"name":1544,"slug":1545,"type":15},"Monitoring","monitoring",{"name":13,"slug":14,"type":15},{"name":1548,"slug":1549,"type":15},"Operations","operations",{"name":9,"slug":8,"type":15},"2026-07-18T05:10:54.430898",{"slug":1553,"name":1553,"fn":1554,"description":1555,"org":1556,"tags":1557,"stars":29,"repoUrl":30,"updatedAt":1568},"building-workflows","build and edit PostHog workflows","Build, edit, test, enable, and monitor PostHog workflows over MCP. Author the action\u002Fedge graph so it runs and opens cleanly in the visual editor, then change drafts surgically with patch operations. Use when asked to build, set up, automate, change, fix, or debug a workflow, campaign, broadcast, drip sequence, or event-triggered automation in the workflows product.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1558,1561,1564,1565],{"name":1559,"slug":1560,"type":15},"Automation","automation",{"name":1562,"slug":1563,"type":15},"MCP","mcp",{"name":9,"slug":8,"type":15},{"name":1566,"slug":1567,"type":15},"Workflow Automation","workflow-automation","2026-07-28T05:34:12.167015",61,{"items":1571,"total":1687},[1572,1579,1585,1592,1599,1606,1614,1621,1635,1650,1660,1677],{"slug":1472,"name":1472,"fn":1473,"description":1474,"org":1573,"tags":1574,"stars":29,"repoUrl":30,"updatedAt":1483},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1575,1576,1577,1578],{"name":24,"slug":25,"type":15},{"name":1479,"slug":1480,"type":15},{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},{"slug":1485,"name":1485,"fn":1486,"description":1487,"org":1580,"tags":1581,"stars":29,"repoUrl":30,"updatedAt":1495},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1582,1583,1584],{"name":24,"slug":25,"type":15},{"name":1492,"slug":1493,"type":15},{"name":9,"slug":8,"type":15},{"slug":1497,"name":1497,"fn":1498,"description":1499,"org":1586,"tags":1587,"stars":29,"repoUrl":30,"updatedAt":1507},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1588,1589,1590,1591],{"name":1492,"slug":1493,"type":15},{"name":1504,"slug":38,"type":15},{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},{"slug":1509,"name":1509,"fn":1510,"description":1511,"org":1593,"tags":1594,"stars":29,"repoUrl":30,"updatedAt":1520},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1595,1596,1597,1598],{"name":1492,"slug":1493,"type":15},{"name":1504,"slug":38,"type":15},{"name":1517,"slug":1518,"type":15},{"name":9,"slug":8,"type":15},{"slug":1522,"name":1522,"fn":1523,"description":1524,"org":1600,"tags":1601,"stars":29,"repoUrl":30,"updatedAt":1535},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1602,1603,1604,1605],{"name":1528,"slug":1529,"type":15},{"name":1531,"slug":1532,"type":15},{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},{"slug":1537,"name":1537,"fn":1538,"description":1539,"org":1607,"tags":1608,"stars":29,"repoUrl":30,"updatedAt":1551},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1609,1610,1611,1612,1613],{"name":24,"slug":25,"type":15},{"name":1544,"slug":1545,"type":15},{"name":13,"slug":14,"type":15},{"name":1548,"slug":1549,"type":15},{"name":9,"slug":8,"type":15},{"slug":1553,"name":1553,"fn":1554,"description":1555,"org":1615,"tags":1616,"stars":29,"repoUrl":30,"updatedAt":1568},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1617,1618,1619,1620],{"name":1559,"slug":1560,"type":15},{"name":1562,"slug":1563,"type":15},{"name":9,"slug":8,"type":15},{"name":1566,"slug":1567,"type":15},{"slug":1622,"name":1622,"fn":1623,"description":1624,"org":1625,"tags":1626,"stars":29,"repoUrl":30,"updatedAt":1634},"check-posthog-loading","inspect PostHog SDK loading across URLs","Inspect how the PostHog JavaScript SDK is loaded across a list of URLs. Use to confirm consistent installation across pages, find pages missing the snippet, detect mismatched API keys or hosts between pages, and verify the load method (head snippet vs deferred vs array.js).\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1627,1628,1629,1632,1633],{"name":24,"slug":25,"type":15},{"name":1531,"slug":1532,"type":15},{"name":1630,"slug":1631,"type":15},"Frontend","frontend",{"name":13,"slug":14,"type":15},{"name":9,"slug":8,"type":15},"2026-05-07T05:56:19.828048",{"slug":1636,"name":1636,"fn":1637,"description":1638,"org":1639,"tags":1640,"stars":29,"repoUrl":30,"updatedAt":1649},"consuming-endpoints-from-client-code","integrate PostHog endpoints into client applications","Wire a PostHog endpoint into a client app or SDK. Covers fetching the OpenAPI spec, generating a typed client with openapi-generator or @hey-api\u002Fopenapi-ts, sending the right auth header, shaping the variables payload (HogQL code_name vs insight breakdown property), handling rate-limit and materialised-endpoint error responses. Use when the user says \"how do I call my endpoint\", \"generate a client for this\", or \"what auth header do I use\".\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1641,1644,1645,1646],{"name":1642,"slug":1643,"type":15},"API Development","api-development",{"name":1630,"slug":1631,"type":15},{"name":9,"slug":8,"type":15},{"name":1647,"slug":1648,"type":15},"SDK","sdk","2026-06-08T08:08:34.929454",{"slug":1651,"name":1651,"fn":1652,"description":1653,"org":1654,"tags":1655,"stars":29,"repoUrl":30,"updatedAt":1659},"copying-endpoints-across-projects","copy PostHog endpoints across projects","Copy a PostHog endpoint (a saved HogQL\u002Finsight query exposed as an API route) to another project in the same organization, or duplicate it under a new name in the same project. Use when the user wants to duplicate an endpoint, promote an endpoint from staging to production, replicate an endpoint's query\u002Fvariables\u002Ffreshness config in another workspace, or clone an endpoint to iterate on it. Unlike feature flags and experiments, endpoints have NO native cross-project copy tool — this skill covers the read-then-recreate flow (endpoint-get then endpoint-create), the active-project switching it requires, name-collision checks, and the safe defaults (land unmaterialised in the target, verify with endpoint-run). Does not cover editing endpoint versions (see managing-endpoint-versions) or authoring a brand-new endpoint from scratch (see creating-an-endpoint).\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1656,1657,1658],{"name":1642,"slug":1643,"type":15},{"name":1548,"slug":1549,"type":15},{"name":9,"slug":8,"type":15},"2026-07-15T05:29:58.442727",{"slug":1335,"name":1335,"fn":1661,"description":1662,"org":1663,"tags":1664,"stars":29,"repoUrl":30,"updatedAt":1676},"schedule recurring AI-generated PostHog reports","Create a recurring AI-generated PostHog report — schedule a free-text prompt to run on a cron, with the LLM-synthesized markdown delivered to email or Slack on each tick. Use when the user wants a recurring AI summary of X on any cadence (daily, weekly, monthly, yearly) rather than a one-off report. (To attach an AI summary to an existing insight\u002Fdashboard subscription instead of a free-text prompt, see `managing-subscriptions` and its `summary_enabled` option.)\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1665,1666,1669,1670,1673],{"name":1559,"slug":1560,"type":15},{"name":1667,"slug":1668,"type":15},"Email","email",{"name":9,"slug":8,"type":15},{"name":1671,"slug":1672,"type":15},"Reporting","reporting",{"name":1674,"slug":1675,"type":15},"Slack","slack","2026-06-09T07:32:27.935712",{"slug":1678,"name":1678,"fn":1679,"description":1680,"org":1681,"tags":1682,"stars":29,"repoUrl":30,"updatedAt":1686},"creating-an-endpoint","create PostHog API endpoints","Create a PostHog endpoint with the right shape on the first try — covers query kind choice, name conventions, what to expose as variables (HogQL code_name vs insight breakdown), data_freshness_seconds, and whether to materialise on day one. Use when the user says \"create an endpoint\", \"expose this query as an API\", \"turn this insight into an endpoint\", or asks for help structuring a new endpoint. Steers away from common mistakes: materialising a query with cohort breakdowns or compare mode, inline-only variables on a materialised endpoint, unbounded date ranges, ambiguous names.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1683,1684,1685],{"name":24,"slug":25,"type":15},{"name":1642,"slug":1643,"type":15},{"name":9,"slug":8,"type":15},"2026-06-08T08:08:29.624498",231]