[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-posthog-investigating-ci-failures":3,"mdc-r9zf8z-key":51,"related-repo-posthog-investigating-ci-failures":854,"related-org-posthog-investigating-ci-failures":956},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":25,"repoUrl":26,"updatedAt":27,"license":28,"forks":29,"topics":30,"repo":46,"sourceUrl":49,"mdContent":50},"investigating-ci-failures","investigate CI failures and identify causes","Investigates a specific CI failure to a verdict: whose fault, which commit, who wrote it, and whether it's fixed. Use for \"who broke master\", \"why did this test fail in CI\", \"is this failure my PR's fault or everyone's\", \"is this test flaky or actually broken\", \"when did this failure start\". Works from the engineering_analytics warehouse views (engineering_analytics_ci_failures, engineering_analytics_ci_job_history) plus the CI failure logs. Not for aggregate CI health, cost, or merge bottlenecks (use diagnosing-ci-and-merge-bottlenecks) and not for building saved insights (use turning-engineering-analytics-into-insights).\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,22],{"name":13,"slug":14,"type":15},"GitHub","github","tag",{"name":17,"slug":18,"type":15},"CI\u002FCD","ci-cd",{"name":20,"slug":21,"type":15},"Code Analysis","code-analysis",{"name":23,"slug":24,"type":15},"Debugging","debugging",35568,"https:\u002F\u002Fgithub.com\u002FPostHog\u002Fposthog","2026-07-31T05:52:01.895332",null,2977,[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],"ab-testing","ai-analytics","analytics","cdp","data-warehouse","experiments","feature-flags","javascript","product-analytics","python","react","session-replay","surveys","typescript","web-analytics",{"repoUrl":26,"stars":25,"forks":29,"topics":47,"description":48},[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],"🦔 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\u002Finvestigating-ci-failures","---\nname: investigating-ci-failures\ndescription: >\n  Investigates a specific CI failure to a verdict: whose fault, which commit, who wrote it, and\n  whether it's fixed. Use for \"who broke master\", \"why did this test fail in CI\", \"is this failure\n  my PR's fault or everyone's\", \"is this test flaky or actually broken\", \"when did this failure\n  start\". Works from the engineering_analytics warehouse views (engineering_analytics_ci_failures,\n  engineering_analytics_ci_job_history) plus the CI failure logs. Not for aggregate CI health, cost,\n  or merge bottlenecks (use diagnosing-ci-and-merge-bottlenecks) and not for building saved insights\n  (use turning-engineering-analytics-into-insights).\n---\n\n# Investigating CI failures\n\nThe job: take one failing test or one red run and get to a verdict a developer can act on —\n_yours \u002F trunk-borne \u002F flaky_, and when trunk-borne: the culprit SHA, its author, the PR, and\nwhether a fix already landed. Everything below is derivation over data that already exists; you\nnever need to re-run CI to answer.\n\nTwo warehouse views are the substrate (both non-materialized — always current, query them freely):\n\n- **`engineering_analytics_ci_failures`** — one row per pytest `FAILED \u003Cnodeid>` line from CI logs,\n  pre-fingerprinted (`fingerprint` = test id + digit\u002Fhex-normalized error). Group by `fingerprint`\n  to get first\u002Flast seen, occurrence count, and branch spread.\n- **`engineering_analytics_ci_job_history`** — one row per job attempt with `conclusion` AND commit\n  attribution: `head_sha`, `commit_author_name`, `commit_message`, `commit_pr_number` (the merged PR\n  that produced the commit, the only PR attribution a master push run has). This is where greens\n  live; the logs are failure-only, so every \"when did it turn red \u002F green again\" question must come\n  from here, never from the logs.\n\nCopy-ready SQL for every step is in [references\u002Finvestigation-queries.md](.\u002Freferences\u002Finvestigation-queries.md).\n\n## Start wide: what's broken right now\n\nFor \"what CI failures should I care about right now\" (before you have a specific test in hand), the\n`engineering-analytics-broken-tests` MCP tool does the shape classification below across _all_ live\nfailures at once: it groups the last 2 days of failures by fingerprint and labels each\n`breaking_master` \u002F `novel_burst` \u002F `potentially_resolved` \u002F `flaky` \u002F `pr_only`, most urgent first,\nplus `breaking_master_jobs` (default-branch jobs whose latest run is red). Use it as the triage entry\npoint, then drop into the per-failure workflow below to reach a culprit. It is the automated\ncounterpart to fingerprinting by hand; the manual queries stay the way to pin a specific failure to a\nboundary and author.\n\n## The three failure shapes\n\nFingerprint the failure first (query 1 in the references), then read its shape — the classification\nfalls out of three columns:\n\n| Shape                                   | Reading                         | Next step                                            |\n| --------------------------------------- | ------------------------------- | ---------------------------------------------------- |\n| 1 branch, any window                    | That PR's own problem           | Read its failure lines; done                         |\n| Many branches, dense burst, hits master | Trunk break (master is\u002Fwas red) | Boundary query → culprit (below)                     |\n| Many branches, sporadic over days\u002Fweeks | Flaky                           | Corroborate with `engineering-analytics-flaky-tests` |\n\nWhy cross-branch means trunk: PR CI runs the PR **merged with master**, so one bad master commit\nfails every concurrently-running PR. A failure appearing on many unrelated branches in a tight\nwindow is the signature of a master-merge break, not of those PRs' code. Tell the asker explicitly\nwhen their PR is not at fault — that is usually the single most valuable sentence in the answer.\n\n## Trunk break → culprit\n\nRun the boundary query (query 2): master-only job history for the failing job, ordered by\n`created_at`. The pattern reads directly:\n\n```text\n... success success | failure failure ... failure | success ...\n                    ^ first red = the culprit row  ^ first green = the fix row\n```\n\nThe culprit row carries everything: `head_sha`, `commit_author_name`, `commit_message` (which names\nwhat changed), `commit_pr_number`. The first-green row identifies the fix the same way. Confidence\ncheck before naming anyone: does the culprit commit plausibly touch the failing area (its message \u002F\nPR diff vs the failing test's module)? A boundary landing on an unrelated commit means sharding or\ntiming noise — widen the window and check the adjacent commit before asserting.\n\nThen verify the failure window in `ci_failures` matches (first_seen just after the culprit merged,\nlast_seen shortly after the fix as the PR queue drained). Mismatch = you're looking at two\ndifferent problems sharing a test.\n\n## Flaky → corroborate, don't guess\n\nSporadic shape alone is suggestive, not proof. The `engineering-analytics-flaky-tests` MCP tool reads per-test CI spans\n(rerun-pass signal — a test that failed then passed on retry in the same job) and is the stronger\nsignal where it has coverage. Counts only, never rates: passing runs below the emitter's duration\nthreshold aren't recorded, so there is no honest denominator.\n\n## Caveats you must carry into every answer\n\n- **The logs are failure-only.** No green baseline exists in `ci_failures`; absence of a\n  fingerprint is weak evidence (the job may simply not have run). Greens come from\n  `ci_job_history` only.\n- **Fingerprints are pytest-only (v1).** Jest \u002F playwright \u002F cargo failures appear in the raw\n  failure logs but are not in `ci_failures`. For those, fall back to grouped triage via the\n  `engineering-analytics-master-failures` \u002F `engineering-analytics-ci-failure-logs` MCP tools.\n- **Freshness differs per source.** Logs stream in near-real-time; the warehouse jobs\u002Fruns tables\n  arrive via webhook sync and can lag. During a live incident, start from `ci_failures` and check\n  the warehouse's `max(created_at)` before trusting a boundary (query 5). A boundary computed\n  against a stale warehouse names the wrong commit.\n- **A run's `conclusion` can be stale** until the `workflow_run` webhook settles it (SPEC §7) —\n  treat a very recent \"failure-free\" tail with suspicion.\n- **Retries:** `run_attempt > 1` rows are the same job re-run. A failure that clears on attempt 2\n  is flake signal; one that fails through attempt 5+ is deterministic.\n- **Reverts:** a revert shows up as a _new_ first-green (or first-red) commit whose\n  `commit_pr_number` is the reverting PR — attribution follows the revert, not the original.\n- **Time-bound every logs query.** The failure-log stream is large; unbounded scans hit the read\n  cap. 14 days covers almost every investigation.\n- **Pair the warehouse twin too.** A `ci_job_history` query windowed on `created_at` alone forces a\n  full jobs scan — the parsed timestamp is a computed column the parquet scan can't prune on. Add a\n  coarse `created_at_raw >= '\u003CYYYY-MM-DD>'` string floor (a day below the window) alongside the\n  precise `created_at` bound so the scan skips; `created_at` stays the exact filter.\n\n## Choosing a surface\n\n| Question                               | Use                                                                    |\n| -------------------------------------- | ---------------------------------------------------------------------- |\n| \"What's broken across CI right now?\"   | `engineering-analytics-broken-tests` MCP tool (triaged, classified)    |\n| \"Why did MY PR's CI fail?\"             | `engineering-analytics-ci-failure-logs` MCP tool (PR-scoped, grouped)  |\n| \"Who broke master \u002F when did X start?\" | The two views, workflow above                                          |\n| \"Is X flaky?\"                          | Shape from `ci_failures` + the flaky-tests tool                        |\n| \"What's failing on master right now?\"  | `engineering-analytics-master-failures` MCP tool (grouped triage feed) |\n| \"Is CI slow \u002F expensive \u002F PRs stuck?\"  | The `diagnosing-ci-and-merge-bottlenecks` skill                        |\n| \"Save this as a dashboard\u002Finsight\"     | The `turning-engineering-analytics-into-insights` skill                |\n\n## Output expectations\n\nLead with the verdict and the exoneration\u002Fblame in plain words (\"not your PR — master was broken\nbetween 08:01 and 09:58 UTC by #68727; fixed by #68855\"), then the evidence: the boundary rows, the\nfingerprint window, occurrence\u002Fbranch counts. Name the author factually (they authored the culprit\ncommit), never accusatorially — the commit message and PR link let the reader judge the change, and\nhalf the time the \"culprit\" was a reasonable change with an unmocked test dependency.\n",{"data":52,"body":53},{"name":4,"description":6},{"type":54,"children":55},"root",[56,64,78,83,179,193,200,265,271,276,369,381,387,400,412,443,456,462,474,480,688,694,843,849],{"type":57,"tag":58,"props":59,"children":60},"element","h1",{"id":4},[61],{"type":62,"value":63},"text","Investigating CI failures",{"type":57,"tag":65,"props":66,"children":67},"p",{},[68,70,76],{"type":62,"value":69},"The job: take one failing test or one red run and get to a verdict a developer can act on —\n",{"type":57,"tag":71,"props":72,"children":73},"em",{},[74],{"type":62,"value":75},"yours \u002F trunk-borne \u002F flaky",{"type":62,"value":77},", and when trunk-borne: the culprit SHA, its author, the PR, and\nwhether a fix already landed. Everything below is derivation over data that already exists; you\nnever need to re-run CI to answer.",{"type":57,"tag":65,"props":79,"children":80},{},[81],{"type":62,"value":82},"Two warehouse views are the substrate (both non-materialized — always current, query them freely):",{"type":57,"tag":84,"props":85,"children":86},"ul",{},[87,127],{"type":57,"tag":88,"props":89,"children":90},"li",{},[91,102,104,110,112,118,120,125],{"type":57,"tag":92,"props":93,"children":94},"strong",{},[95],{"type":57,"tag":96,"props":97,"children":99},"code",{"className":98},[],[100],{"type":62,"value":101},"engineering_analytics_ci_failures",{"type":62,"value":103}," — one row per pytest ",{"type":57,"tag":96,"props":105,"children":107},{"className":106},[],[108],{"type":62,"value":109},"FAILED \u003Cnodeid>",{"type":62,"value":111}," line from CI logs,\npre-fingerprinted (",{"type":57,"tag":96,"props":113,"children":115},{"className":114},[],[116],{"type":62,"value":117},"fingerprint",{"type":62,"value":119}," = test id + digit\u002Fhex-normalized error). Group by ",{"type":57,"tag":96,"props":121,"children":123},{"className":122},[],[124],{"type":62,"value":117},{"type":62,"value":126},"\nto get first\u002Flast seen, occurrence count, and branch spread.",{"type":57,"tag":88,"props":128,"children":129},{},[130,139,141,147,149,155,157,163,164,170,171,177],{"type":57,"tag":92,"props":131,"children":132},{},[133],{"type":57,"tag":96,"props":134,"children":136},{"className":135},[],[137],{"type":62,"value":138},"engineering_analytics_ci_job_history",{"type":62,"value":140}," — one row per job attempt with ",{"type":57,"tag":96,"props":142,"children":144},{"className":143},[],[145],{"type":62,"value":146},"conclusion",{"type":62,"value":148}," AND commit\nattribution: ",{"type":57,"tag":96,"props":150,"children":152},{"className":151},[],[153],{"type":62,"value":154},"head_sha",{"type":62,"value":156},", ",{"type":57,"tag":96,"props":158,"children":160},{"className":159},[],[161],{"type":62,"value":162},"commit_author_name",{"type":62,"value":156},{"type":57,"tag":96,"props":165,"children":167},{"className":166},[],[168],{"type":62,"value":169},"commit_message",{"type":62,"value":156},{"type":57,"tag":96,"props":172,"children":174},{"className":173},[],[175],{"type":62,"value":176},"commit_pr_number",{"type":62,"value":178}," (the merged PR\nthat produced the commit, the only PR attribution a master push run has). This is where greens\nlive; the logs are failure-only, so every \"when did it turn red \u002F green again\" question must come\nfrom here, never from the logs.",{"type":57,"tag":65,"props":180,"children":181},{},[182,184,191],{"type":62,"value":183},"Copy-ready SQL for every step is in ",{"type":57,"tag":185,"props":186,"children":188},"a",{"href":187},".\u002Freferences\u002Finvestigation-queries.md",[189],{"type":62,"value":190},"references\u002Finvestigation-queries.md",{"type":62,"value":192},".",{"type":57,"tag":194,"props":195,"children":197},"h2",{"id":196},"start-wide-whats-broken-right-now",[198],{"type":62,"value":199},"Start wide: what's broken right now",{"type":57,"tag":65,"props":201,"children":202},{},[203,205,211,213,218,220,226,228,234,235,241,242,248,249,255,257,263],{"type":62,"value":204},"For \"what CI failures should I care about right now\" (before you have a specific test in hand), the\n",{"type":57,"tag":96,"props":206,"children":208},{"className":207},[],[209],{"type":62,"value":210},"engineering-analytics-broken-tests",{"type":62,"value":212}," MCP tool does the shape classification below across ",{"type":57,"tag":71,"props":214,"children":215},{},[216],{"type":62,"value":217},"all",{"type":62,"value":219}," live\nfailures at once: it groups the last 2 days of failures by fingerprint and labels each\n",{"type":57,"tag":96,"props":221,"children":223},{"className":222},[],[224],{"type":62,"value":225},"breaking_master",{"type":62,"value":227}," \u002F ",{"type":57,"tag":96,"props":229,"children":231},{"className":230},[],[232],{"type":62,"value":233},"novel_burst",{"type":62,"value":227},{"type":57,"tag":96,"props":236,"children":238},{"className":237},[],[239],{"type":62,"value":240},"potentially_resolved",{"type":62,"value":227},{"type":57,"tag":96,"props":243,"children":245},{"className":244},[],[246],{"type":62,"value":247},"flaky",{"type":62,"value":227},{"type":57,"tag":96,"props":250,"children":252},{"className":251},[],[253],{"type":62,"value":254},"pr_only",{"type":62,"value":256},", most urgent first,\nplus ",{"type":57,"tag":96,"props":258,"children":260},{"className":259},[],[261],{"type":62,"value":262},"breaking_master_jobs",{"type":62,"value":264}," (default-branch jobs whose latest run is red). Use it as the triage entry\npoint, then drop into the per-failure workflow below to reach a culprit. It is the automated\ncounterpart to fingerprinting by hand; the manual queries stay the way to pin a specific failure to a\nboundary and author.",{"type":57,"tag":194,"props":266,"children":268},{"id":267},"the-three-failure-shapes",[269],{"type":62,"value":270},"The three failure shapes",{"type":57,"tag":65,"props":272,"children":273},{},[274],{"type":62,"value":275},"Fingerprint the failure first (query 1 in the references), then read its shape — the classification\nfalls out of three columns:",{"type":57,"tag":277,"props":278,"children":279},"table",{},[280,304],{"type":57,"tag":281,"props":282,"children":283},"thead",{},[284],{"type":57,"tag":285,"props":286,"children":287},"tr",{},[288,294,299],{"type":57,"tag":289,"props":290,"children":291},"th",{},[292],{"type":62,"value":293},"Shape",{"type":57,"tag":289,"props":295,"children":296},{},[297],{"type":62,"value":298},"Reading",{"type":57,"tag":289,"props":300,"children":301},{},[302],{"type":62,"value":303},"Next step",{"type":57,"tag":305,"props":306,"children":307},"tbody",{},[308,327,345],{"type":57,"tag":285,"props":309,"children":310},{},[311,317,322],{"type":57,"tag":312,"props":313,"children":314},"td",{},[315],{"type":62,"value":316},"1 branch, any window",{"type":57,"tag":312,"props":318,"children":319},{},[320],{"type":62,"value":321},"That PR's own problem",{"type":57,"tag":312,"props":323,"children":324},{},[325],{"type":62,"value":326},"Read its failure lines; done",{"type":57,"tag":285,"props":328,"children":329},{},[330,335,340],{"type":57,"tag":312,"props":331,"children":332},{},[333],{"type":62,"value":334},"Many branches, dense burst, hits master",{"type":57,"tag":312,"props":336,"children":337},{},[338],{"type":62,"value":339},"Trunk break (master is\u002Fwas red)",{"type":57,"tag":312,"props":341,"children":342},{},[343],{"type":62,"value":344},"Boundary query → culprit (below)",{"type":57,"tag":285,"props":346,"children":347},{},[348,353,358],{"type":57,"tag":312,"props":349,"children":350},{},[351],{"type":62,"value":352},"Many branches, sporadic over days\u002Fweeks",{"type":57,"tag":312,"props":354,"children":355},{},[356],{"type":62,"value":357},"Flaky",{"type":57,"tag":312,"props":359,"children":360},{},[361,363],{"type":62,"value":362},"Corroborate with ",{"type":57,"tag":96,"props":364,"children":366},{"className":365},[],[367],{"type":62,"value":368},"engineering-analytics-flaky-tests",{"type":57,"tag":65,"props":370,"children":371},{},[372,374,379],{"type":62,"value":373},"Why cross-branch means trunk: PR CI runs the PR ",{"type":57,"tag":92,"props":375,"children":376},{},[377],{"type":62,"value":378},"merged with master",{"type":62,"value":380},", so one bad master commit\nfails every concurrently-running PR. A failure appearing on many unrelated branches in a tight\nwindow is the signature of a master-merge break, not of those PRs' code. Tell the asker explicitly\nwhen their PR is not at fault — that is usually the single most valuable sentence in the answer.",{"type":57,"tag":194,"props":382,"children":384},{"id":383},"trunk-break-culprit",[385],{"type":62,"value":386},"Trunk break → culprit",{"type":57,"tag":65,"props":388,"children":389},{},[390,392,398],{"type":62,"value":391},"Run the boundary query (query 2): master-only job history for the failing job, ordered by\n",{"type":57,"tag":96,"props":393,"children":395},{"className":394},[],[396],{"type":62,"value":397},"created_at",{"type":62,"value":399},". The pattern reads directly:",{"type":57,"tag":401,"props":402,"children":407},"pre",{"className":403,"code":405,"language":62,"meta":406},[404],"language-text","... success success | failure failure ... failure | success ...\n                    ^ first red = the culprit row  ^ first green = the fix row\n","",[408],{"type":57,"tag":96,"props":409,"children":410},{"__ignoreMap":406},[411],{"type":62,"value":405},{"type":57,"tag":65,"props":413,"children":414},{},[415,417,422,423,428,429,434,436,441],{"type":62,"value":416},"The culprit row carries everything: ",{"type":57,"tag":96,"props":418,"children":420},{"className":419},[],[421],{"type":62,"value":154},{"type":62,"value":156},{"type":57,"tag":96,"props":424,"children":426},{"className":425},[],[427],{"type":62,"value":162},{"type":62,"value":156},{"type":57,"tag":96,"props":430,"children":432},{"className":431},[],[433],{"type":62,"value":169},{"type":62,"value":435}," (which names\nwhat changed), ",{"type":57,"tag":96,"props":437,"children":439},{"className":438},[],[440],{"type":62,"value":176},{"type":62,"value":442},". The first-green row identifies the fix the same way. Confidence\ncheck before naming anyone: does the culprit commit plausibly touch the failing area (its message \u002F\nPR diff vs the failing test's module)? A boundary landing on an unrelated commit means sharding or\ntiming noise — widen the window and check the adjacent commit before asserting.",{"type":57,"tag":65,"props":444,"children":445},{},[446,448,454],{"type":62,"value":447},"Then verify the failure window in ",{"type":57,"tag":96,"props":449,"children":451},{"className":450},[],[452],{"type":62,"value":453},"ci_failures",{"type":62,"value":455}," matches (first_seen just after the culprit merged,\nlast_seen shortly after the fix as the PR queue drained). Mismatch = you're looking at two\ndifferent problems sharing a test.",{"type":57,"tag":194,"props":457,"children":459},{"id":458},"flaky-corroborate-dont-guess",[460],{"type":62,"value":461},"Flaky → corroborate, don't guess",{"type":57,"tag":65,"props":463,"children":464},{},[465,467,472],{"type":62,"value":466},"Sporadic shape alone is suggestive, not proof. The ",{"type":57,"tag":96,"props":468,"children":470},{"className":469},[],[471],{"type":62,"value":368},{"type":62,"value":473}," MCP tool reads per-test CI spans\n(rerun-pass signal — a test that failed then passed on retry in the same job) and is the stronger\nsignal where it has coverage. Counts only, never rates: passing runs below the emitter's duration\nthreshold aren't recorded, so there is no honest denominator.",{"type":57,"tag":194,"props":475,"children":477},{"id":476},"caveats-you-must-carry-into-every-answer",[478],{"type":62,"value":479},"Caveats you must carry into every answer",{"type":57,"tag":84,"props":481,"children":482},{},[483,508,540,565,590,608,632,642],{"type":57,"tag":88,"props":484,"children":485},{},[486,491,493,498,500,506],{"type":57,"tag":92,"props":487,"children":488},{},[489],{"type":62,"value":490},"The logs are failure-only.",{"type":62,"value":492}," No green baseline exists in ",{"type":57,"tag":96,"props":494,"children":496},{"className":495},[],[497],{"type":62,"value":453},{"type":62,"value":499},"; absence of a\nfingerprint is weak evidence (the job may simply not have run). Greens come from\n",{"type":57,"tag":96,"props":501,"children":503},{"className":502},[],[504],{"type":62,"value":505},"ci_job_history",{"type":62,"value":507}," only.",{"type":57,"tag":88,"props":509,"children":510},{},[511,516,518,523,525,531,532,538],{"type":57,"tag":92,"props":512,"children":513},{},[514],{"type":62,"value":515},"Fingerprints are pytest-only (v1).",{"type":62,"value":517}," Jest \u002F playwright \u002F cargo failures appear in the raw\nfailure logs but are not in ",{"type":57,"tag":96,"props":519,"children":521},{"className":520},[],[522],{"type":62,"value":453},{"type":62,"value":524},". For those, fall back to grouped triage via the\n",{"type":57,"tag":96,"props":526,"children":528},{"className":527},[],[529],{"type":62,"value":530},"engineering-analytics-master-failures",{"type":62,"value":227},{"type":57,"tag":96,"props":533,"children":535},{"className":534},[],[536],{"type":62,"value":537},"engineering-analytics-ci-failure-logs",{"type":62,"value":539}," MCP tools.",{"type":57,"tag":88,"props":541,"children":542},{},[543,548,550,555,557,563],{"type":57,"tag":92,"props":544,"children":545},{},[546],{"type":62,"value":547},"Freshness differs per source.",{"type":62,"value":549}," Logs stream in near-real-time; the warehouse jobs\u002Fruns tables\narrive via webhook sync and can lag. During a live incident, start from ",{"type":57,"tag":96,"props":551,"children":553},{"className":552},[],[554],{"type":62,"value":453},{"type":62,"value":556}," and check\nthe warehouse's ",{"type":57,"tag":96,"props":558,"children":560},{"className":559},[],[561],{"type":62,"value":562},"max(created_at)",{"type":62,"value":564}," before trusting a boundary (query 5). A boundary computed\nagainst a stale warehouse names the wrong commit.",{"type":57,"tag":88,"props":566,"children":567},{},[568,580,582,588],{"type":57,"tag":92,"props":569,"children":570},{},[571,573,578],{"type":62,"value":572},"A run's ",{"type":57,"tag":96,"props":574,"children":576},{"className":575},[],[577],{"type":62,"value":146},{"type":62,"value":579}," can be stale",{"type":62,"value":581}," until the ",{"type":57,"tag":96,"props":583,"children":585},{"className":584},[],[586],{"type":62,"value":587},"workflow_run",{"type":62,"value":589}," webhook settles it (SPEC §7) —\ntreat a very recent \"failure-free\" tail with suspicion.",{"type":57,"tag":88,"props":591,"children":592},{},[593,598,600,606],{"type":57,"tag":92,"props":594,"children":595},{},[596],{"type":62,"value":597},"Retries:",{"type":62,"value":599}," ",{"type":57,"tag":96,"props":601,"children":603},{"className":602},[],[604],{"type":62,"value":605},"run_attempt > 1",{"type":62,"value":607}," rows are the same job re-run. A failure that clears on attempt 2\nis flake signal; one that fails through attempt 5+ is deterministic.",{"type":57,"tag":88,"props":609,"children":610},{},[611,616,618,623,625,630],{"type":57,"tag":92,"props":612,"children":613},{},[614],{"type":62,"value":615},"Reverts:",{"type":62,"value":617}," a revert shows up as a ",{"type":57,"tag":71,"props":619,"children":620},{},[621],{"type":62,"value":622},"new",{"type":62,"value":624}," first-green (or first-red) commit whose\n",{"type":57,"tag":96,"props":626,"children":628},{"className":627},[],[629],{"type":62,"value":176},{"type":62,"value":631}," is the reverting PR — attribution follows the revert, not the original.",{"type":57,"tag":88,"props":633,"children":634},{},[635,640],{"type":57,"tag":92,"props":636,"children":637},{},[638],{"type":62,"value":639},"Time-bound every logs query.",{"type":62,"value":641}," The failure-log stream is large; unbounded scans hit the read\ncap. 14 days covers almost every investigation.",{"type":57,"tag":88,"props":643,"children":644},{},[645,650,652,657,659,664,666,672,674,679,681,686],{"type":57,"tag":92,"props":646,"children":647},{},[648],{"type":62,"value":649},"Pair the warehouse twin too.",{"type":62,"value":651}," A ",{"type":57,"tag":96,"props":653,"children":655},{"className":654},[],[656],{"type":62,"value":505},{"type":62,"value":658}," query windowed on ",{"type":57,"tag":96,"props":660,"children":662},{"className":661},[],[663],{"type":62,"value":397},{"type":62,"value":665}," alone forces a\nfull jobs scan — the parsed timestamp is a computed column the parquet scan can't prune on. Add a\ncoarse ",{"type":57,"tag":96,"props":667,"children":669},{"className":668},[],[670],{"type":62,"value":671},"created_at_raw >= '\u003CYYYY-MM-DD>'",{"type":62,"value":673}," string floor (a day below the window) alongside the\nprecise ",{"type":57,"tag":96,"props":675,"children":677},{"className":676},[],[678],{"type":62,"value":397},{"type":62,"value":680}," bound so the scan skips; ",{"type":57,"tag":96,"props":682,"children":684},{"className":683},[],[685],{"type":62,"value":397},{"type":62,"value":687}," stays the exact filter.",{"type":57,"tag":194,"props":689,"children":691},{"id":690},"choosing-a-surface",[692],{"type":62,"value":693},"Choosing a surface",{"type":57,"tag":277,"props":695,"children":696},{},[697,713],{"type":57,"tag":281,"props":698,"children":699},{},[700],{"type":57,"tag":285,"props":701,"children":702},{},[703,708],{"type":57,"tag":289,"props":704,"children":705},{},[706],{"type":62,"value":707},"Question",{"type":57,"tag":289,"props":709,"children":710},{},[711],{"type":62,"value":712},"Use",{"type":57,"tag":305,"props":714,"children":715},{},[716,734,752,765,785,803,824],{"type":57,"tag":285,"props":717,"children":718},{},[719,724],{"type":57,"tag":312,"props":720,"children":721},{},[722],{"type":62,"value":723},"\"What's broken across CI right now?\"",{"type":57,"tag":312,"props":725,"children":726},{},[727,732],{"type":57,"tag":96,"props":728,"children":730},{"className":729},[],[731],{"type":62,"value":210},{"type":62,"value":733}," MCP tool (triaged, classified)",{"type":57,"tag":285,"props":735,"children":736},{},[737,742],{"type":57,"tag":312,"props":738,"children":739},{},[740],{"type":62,"value":741},"\"Why did MY PR's CI fail?\"",{"type":57,"tag":312,"props":743,"children":744},{},[745,750],{"type":57,"tag":96,"props":746,"children":748},{"className":747},[],[749],{"type":62,"value":537},{"type":62,"value":751}," MCP tool (PR-scoped, grouped)",{"type":57,"tag":285,"props":753,"children":754},{},[755,760],{"type":57,"tag":312,"props":756,"children":757},{},[758],{"type":62,"value":759},"\"Who broke master \u002F when did X start?\"",{"type":57,"tag":312,"props":761,"children":762},{},[763],{"type":62,"value":764},"The two views, workflow above",{"type":57,"tag":285,"props":766,"children":767},{},[768,773],{"type":57,"tag":312,"props":769,"children":770},{},[771],{"type":62,"value":772},"\"Is X flaky?\"",{"type":57,"tag":312,"props":774,"children":775},{},[776,778,783],{"type":62,"value":777},"Shape from ",{"type":57,"tag":96,"props":779,"children":781},{"className":780},[],[782],{"type":62,"value":453},{"type":62,"value":784}," + the flaky-tests tool",{"type":57,"tag":285,"props":786,"children":787},{},[788,793],{"type":57,"tag":312,"props":789,"children":790},{},[791],{"type":62,"value":792},"\"What's failing on master right now?\"",{"type":57,"tag":312,"props":794,"children":795},{},[796,801],{"type":57,"tag":96,"props":797,"children":799},{"className":798},[],[800],{"type":62,"value":530},{"type":62,"value":802}," MCP tool (grouped triage feed)",{"type":57,"tag":285,"props":804,"children":805},{},[806,811],{"type":57,"tag":312,"props":807,"children":808},{},[809],{"type":62,"value":810},"\"Is CI slow \u002F expensive \u002F PRs stuck?\"",{"type":57,"tag":312,"props":812,"children":813},{},[814,816,822],{"type":62,"value":815},"The ",{"type":57,"tag":96,"props":817,"children":819},{"className":818},[],[820],{"type":62,"value":821},"diagnosing-ci-and-merge-bottlenecks",{"type":62,"value":823}," skill",{"type":57,"tag":285,"props":825,"children":826},{},[827,832],{"type":57,"tag":312,"props":828,"children":829},{},[830],{"type":62,"value":831},"\"Save this as a dashboard\u002Finsight\"",{"type":57,"tag":312,"props":833,"children":834},{},[835,836,842],{"type":62,"value":815},{"type":57,"tag":96,"props":837,"children":839},{"className":838},[],[840],{"type":62,"value":841},"turning-engineering-analytics-into-insights",{"type":62,"value":823},{"type":57,"tag":194,"props":844,"children":846},{"id":845},"output-expectations",[847],{"type":62,"value":848},"Output expectations",{"type":57,"tag":65,"props":850,"children":851},{},[852],{"type":62,"value":853},"Lead with the verdict and the exoneration\u002Fblame in plain words (\"not your PR — master was broken\nbetween 08:01 and 09:58 UTC by #68727; fixed by #68855\"), then the evidence: the boundary rows, the\nfingerprint window, occurrence\u002Fbranch counts. Name the author factually (they authored the culprit\ncommit), never accusatorially — the commit message and PR link let the reader judge the change, and\nhalf the time the \"culprit\" was a reasonable change with an unmocked test dependency.",{"items":855,"total":955},[856,872,884,896,909,922,938],{"slug":857,"name":857,"fn":858,"description":859,"org":860,"tags":861,"stars":25,"repoUrl":26,"updatedAt":871},"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},[862,864,867,870],{"name":863,"slug":33,"type":15},"Analytics",{"name":865,"slug":866,"type":15},"Cost Optimization","cost-optimization",{"name":868,"slug":869,"type":15},"Observability","observability",{"name":9,"slug":8,"type":15},"2026-07-28T05:34:11.117757",{"slug":873,"name":873,"fn":874,"description":875,"org":876,"tags":877,"stars":25,"repoUrl":26,"updatedAt":883},"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},[878,879,882],{"name":863,"slug":33,"type":15},{"name":880,"slug":881,"type":15},"Audit","audit",{"name":9,"slug":8,"type":15},"2026-06-08T08:08:33.693989",{"slug":885,"name":885,"fn":886,"description":887,"org":888,"tags":889,"stars":25,"repoUrl":26,"updatedAt":895},"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},[890,891,893,894],{"name":880,"slug":881,"type":15},{"name":892,"slug":35,"type":15},"Data Warehouse",{"name":868,"slug":869,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:22:57.67984",{"slug":897,"name":897,"fn":898,"description":899,"org":900,"tags":901,"stars":25,"repoUrl":26,"updatedAt":908},"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},[902,903,904,907],{"name":880,"slug":881,"type":15},{"name":892,"slug":35,"type":15},{"name":905,"slug":906,"type":15},"Performance","performance",{"name":9,"slug":8,"type":15},"2026-06-18T08:25:10.936787",{"slug":910,"name":910,"fn":911,"description":912,"org":913,"tags":914,"stars":25,"repoUrl":26,"updatedAt":921},"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},[915,918,919,920],{"name":916,"slug":917,"type":15},"Alerting","alerting",{"name":23,"slug":24,"type":15},{"name":868,"slug":869,"type":15},{"name":9,"slug":8,"type":15},"2026-06-18T08:24:40.318583",{"slug":923,"name":923,"fn":924,"description":925,"org":926,"tags":927,"stars":25,"repoUrl":26,"updatedAt":937},"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},[928,929,932,933,936],{"name":863,"slug":33,"type":15},{"name":930,"slug":931,"type":15},"Monitoring","monitoring",{"name":868,"slug":869,"type":15},{"name":934,"slug":935,"type":15},"Operations","operations",{"name":9,"slug":8,"type":15},"2026-07-18T05:10:54.430898",{"slug":939,"name":939,"fn":940,"description":941,"org":942,"tags":943,"stars":25,"repoUrl":26,"updatedAt":954},"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},[944,947,950,951],{"name":945,"slug":946,"type":15},"Automation","automation",{"name":948,"slug":949,"type":15},"MCP","mcp",{"name":9,"slug":8,"type":15},{"name":952,"slug":953,"type":15},"Workflow Automation","workflow-automation","2026-07-28T05:34:12.167015",61,{"items":957,"total":1074},[958,965,971,978,985,992,1000,1007,1021,1036,1046,1064],{"slug":857,"name":857,"fn":858,"description":859,"org":959,"tags":960,"stars":25,"repoUrl":26,"updatedAt":871},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[961,962,963,964],{"name":863,"slug":33,"type":15},{"name":865,"slug":866,"type":15},{"name":868,"slug":869,"type":15},{"name":9,"slug":8,"type":15},{"slug":873,"name":873,"fn":874,"description":875,"org":966,"tags":967,"stars":25,"repoUrl":26,"updatedAt":883},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[968,969,970],{"name":863,"slug":33,"type":15},{"name":880,"slug":881,"type":15},{"name":9,"slug":8,"type":15},{"slug":885,"name":885,"fn":886,"description":887,"org":972,"tags":973,"stars":25,"repoUrl":26,"updatedAt":895},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[974,975,976,977],{"name":880,"slug":881,"type":15},{"name":892,"slug":35,"type":15},{"name":868,"slug":869,"type":15},{"name":9,"slug":8,"type":15},{"slug":897,"name":897,"fn":898,"description":899,"org":979,"tags":980,"stars":25,"repoUrl":26,"updatedAt":908},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[981,982,983,984],{"name":880,"slug":881,"type":15},{"name":892,"slug":35,"type":15},{"name":905,"slug":906,"type":15},{"name":9,"slug":8,"type":15},{"slug":910,"name":910,"fn":911,"description":912,"org":986,"tags":987,"stars":25,"repoUrl":26,"updatedAt":921},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[988,989,990,991],{"name":916,"slug":917,"type":15},{"name":23,"slug":24,"type":15},{"name":868,"slug":869,"type":15},{"name":9,"slug":8,"type":15},{"slug":923,"name":923,"fn":924,"description":925,"org":993,"tags":994,"stars":25,"repoUrl":26,"updatedAt":937},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[995,996,997,998,999],{"name":863,"slug":33,"type":15},{"name":930,"slug":931,"type":15},{"name":868,"slug":869,"type":15},{"name":934,"slug":935,"type":15},{"name":9,"slug":8,"type":15},{"slug":939,"name":939,"fn":940,"description":941,"org":1001,"tags":1002,"stars":25,"repoUrl":26,"updatedAt":954},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1003,1004,1005,1006],{"name":945,"slug":946,"type":15},{"name":948,"slug":949,"type":15},{"name":9,"slug":8,"type":15},{"name":952,"slug":953,"type":15},{"slug":1008,"name":1008,"fn":1009,"description":1010,"org":1011,"tags":1012,"stars":25,"repoUrl":26,"updatedAt":1020},"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},[1013,1014,1015,1018,1019],{"name":863,"slug":33,"type":15},{"name":23,"slug":24,"type":15},{"name":1016,"slug":1017,"type":15},"Frontend","frontend",{"name":868,"slug":869,"type":15},{"name":9,"slug":8,"type":15},"2026-05-07T05:56:19.828048",{"slug":1022,"name":1022,"fn":1023,"description":1024,"org":1025,"tags":1026,"stars":25,"repoUrl":26,"updatedAt":1035},"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},[1027,1030,1031,1032],{"name":1028,"slug":1029,"type":15},"API Development","api-development",{"name":1016,"slug":1017,"type":15},{"name":9,"slug":8,"type":15},{"name":1033,"slug":1034,"type":15},"SDK","sdk","2026-06-08T08:08:34.929454",{"slug":1037,"name":1037,"fn":1038,"description":1039,"org":1040,"tags":1041,"stars":25,"repoUrl":26,"updatedAt":1045},"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},[1042,1043,1044],{"name":1028,"slug":1029,"type":15},{"name":934,"slug":935,"type":15},{"name":9,"slug":8,"type":15},"2026-07-15T05:29:58.442727",{"slug":1047,"name":1047,"fn":1048,"description":1049,"org":1050,"tags":1051,"stars":25,"repoUrl":26,"updatedAt":1063},"creating-ai-subscription","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},[1052,1053,1056,1057,1060],{"name":945,"slug":946,"type":15},{"name":1054,"slug":1055,"type":15},"Email","email",{"name":9,"slug":8,"type":15},{"name":1058,"slug":1059,"type":15},"Reporting","reporting",{"name":1061,"slug":1062,"type":15},"Slack","slack","2026-06-09T07:32:27.935712",{"slug":1065,"name":1065,"fn":1066,"description":1067,"org":1068,"tags":1069,"stars":25,"repoUrl":26,"updatedAt":1073},"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},[1070,1071,1072],{"name":863,"slug":33,"type":15},{"name":1028,"slug":1029,"type":15},{"name":9,"slug":8,"type":15},"2026-06-08T08:08:29.624498",231]