Skip to content
Development
Skill

/investigating-ci-failures

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

From plugin
ai-plugin
95180 skills1 agent3 commands3 hooks
+1

Claude Code

$ npx -y skills add PostHog/ai-plugin --skill investigating-ci-failures --agent claude-code

SKILL.md

investigating-ci-failures.SKILL.md
name: investigating-ci-failures
description: >
  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).

Investigating CI failures

The job: take one failing test or one red run and get to a verdict a developer can act on — _yours / trunk-borne / flaky_, and when trunk-borne: the culprit SHA, its author, the PR, and whether a fix already landed. Everything below is derivation over data that already exists; you never need to re-run CI to answer.

Two warehouse views are the substrate (both non-materialized — always current, query them freely):

  • **`engineering_analytics_ci_failures`** — one row per pytest `FAILED <nodeid>` line from CI logs,

pre-fingerprinted (`fingerprint` = test id + digit/hex-normalized error). Group by `fingerprint` to get first/last seen, occurrence count, and branch spread.

  • **`engineering_analytics_ci_job_history`** — one row per job attempt with `conclusion` AND commit

attribution: `head_sha`, `commit_author_name`, `commit_message`, `commit_pr_number` (the merged PR that produced the commit, the only PR attribution a master push run has). This is where greens live; the logs are failure-only, so every "when did it turn red / green again" question must come from here, never from the logs.

Copy-ready SQL for every step is in [references/investigation-queries.md](./references/investigation-queries.md).

Start wide: what's broken right now

For "what CI failures should I care about right now" (before you have a specific test in hand), the `engineering-analytics-broken-tests` MCP tool does the shape classification below across _all_ live failures at once: it groups the last 2 days of failures by fingerprint and labels each `breaking_master` / `blocking_merge_queue` / `novel_burst` / `potentially_resolved` / `flaky` / `pr_only`, most urgent first, plus `breaking_master_jobs` (default-branch jobs whose latest run is red). Use it as the triage entry point, then drop into the per-failure workflow below to reach a culprit. It is the automated counterpart to fingerprinting by hand; the manual queries stay the way to pin a specific failure to a boundary and author.

`blocking_merge_queue` is the one shape the manual table below does not cover, because it looks like a single-branch failure and is not. The merge queue runs the full suite on a gate branch (`trunk-merge/pr-<n>/…`) carrying master, the PR, and every PR queued ahead of it with overlapping impacted targets (in practice most of the queue), so a failure there is on a commit that already passed the PR's own CI: a conflict with what landed or queued in between, or a queue-mate's own break, not that PR's bug by default. Read it as "this stopped a merge", list what the gate branch ran (query 8), and diff it against trunk rather than reading the PR alone.

The four failure shapes

Fingerprint the failure first (query 1 in the references), then read its shape — the classification falls out of three columns:

| Shape | Reading | Next step | | --------------------------------------- | ------------------------------- | ---------------------------------------------------- | | 1 branch, any window | That PR's own problem | Read its failure lines; done | | 1 `trunk-merge/pr-<n>/…` gate branch | Queue-mate, or landed since | Query 8, then diff the gate branch against trunk | | Many branches, dense burst, hits master | Trunk break (master is/was red) | Boundary query → culprit (below) | | Many branches, sporadic over days/weeks | Flaky | Corroborate with `engineering-analytics-flaky-tests` |

Why cross-branch means trunk: PR CI runs the PR **merged with master**, so one bad master commit fails every concurrently-running PR. A failure appearing on many unrelated branches in a tight window is the signature of a master-merge break, not of those PRs' code. Tell the asker explicitly when their PR is not at fault — that is usually the single most valuable sentence in the answer.

Trunk break → culprit

Run the boundary query (query 2): master-only job history for the failing job, ordered by `created_at`. The pattern reads directly:

... success success | failure failure ... failure | success ...
                    ^ first red = the culprit row  ^ first green = the fix row

The culprit row carries everything: `head_sha`, `commit_author_name`, `commit_message` (which names what changed), `commit_pr_number`. The first-green row identifies the fix the same way. Confidence check before naming anyone: does the culprit commit plausibly touch the failing area (its message / PR diff vs the failing test's module)? A boundary landing on an unrelated commit means sharding or timing noise — widen the window and check the adjacent commit before asserting.

Then verify the failure window in `ci_failures` matches (first_seen just after the culprit merged, last_seen shortly after the fix as the PR queue drained). Mismatch = you're looking at two different problems sharing a test.

Flaky → corroborate, don't guess

**Read the sibling attempts before you say "flaky".** One run cannot separate a flake from a deterministic failure, and `ci_job_history` already carries every attempt's `conclusion` for the branch. If every run o

Read more
@posthog
Ships withai-plugin

Official PostHog plugin for AI clients. Access PostHog products directly from your AI coding tool.

95180 skills1 agent1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
95
Star history of PostHog/ai-plugin on GitHub, a chart by star-history.com

Other skills on ai-plugin.

analyzing-ai-citations

analyzing-ai-citations

Answer AI citation and answer engine optimization (AEO) questions in PostHog: whether AI answer engines (ChatGPT, Claude, and an…

@posthog@posthogInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about investigating-ci-failures.

Which coding agents can use investigating-ci-failures?
investigating-ci-failures installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.