CI Failure Triage Bots: Separate Flaky Noise From Real Regressions

CI Failure Triage Bots: Separate Flaky Noise From Real Regressions
8 views

This guide covers CI failure triage bots with production-focused examples and trade-offs.

Not every red X deserves a page. A triage bot that cannot tell flake from regression will either spam on-call or silently rerun real breakages until the deploy train derails. Classify failures with signatures from history, auto-rerun only the flake class, and escalate novel signatures hard.
⚡ TL;DR: Fingerprint failures (test id + error class + stack top frames). Train/maintain a classifier on labeled history: flake | regression | infra | novel. Auto-rerun flakes with a budget; never auto-rerun regression or novel. Page on novel post-deploy signatures. Pair with AI Test Generation: Property Tests and OpenTelemetry for LLMs for correlating CI with prod.

Fingerprints beat raw log dumps — CI failure triage bots

typescript
import { createHash } from "node:crypto";

export function fingerprint(fail: {
  testId: string;
  message: string;
  stack: string[];
}): string {
  const top = fail.stack.slice(0, 3).map(normalizeFrame).join("|");
  const msgClass = fail.message.replace(/\d+/g, "N").replace(/0x[0-9a-f]+/gi, "HEX");
  return createHash("sha256")
    .update(`${fail.testId}::${msgClass}::${top}`)
    .digest("hex")
    .slice(0, 16);
}

function normalizeFrame(f: string) {
  return f.replace(/:\d+:\d+/g, ":L:C").replace(/\/home\/runner\/.*/g, "REPO");
}
✅ Stable across line-number jitter and runner paths.
❌ Hashing the entire log (unique every time → everything looks novel).

Classifier with an explicit novel bucket

typescript
type Label = "flake" | "regression" | "infra" | "novel";

export async function classify(fp: string, ctx: {
  seenCount90d: number;
  flakeRatio90d: number;
  firstSeenAfterDeploy: boolean;
  matchedInfraSignature: boolean;
}): Promise<Label> {
  if (ctx.matchedInfraSignature) return "infra";
  if (ctx.seenCount90d === 0) return "novel";
  if (ctx.flakeRatio90d >= 0.6 && !ctx.firstSeenAfterDeploy) return "flake";
  if (ctx.firstSeenAfterDeploy && ctx.flakeRatio90d < 0.2) return "regression";
  // LLM assist for ambiguous mid-band — optional, never sole authority
  return "novel"; // fail closed toward human eyes
}
Use historical CI data; an LLM can narrate but must not be the only voter on auto-rerun. Ground narratives like LLM Incident Runbooks.

Actions by class

yaml
flake:
  action: rerun_job
  max_reruns: 1
  notify: silent_metric
regression:
  action: block_merge
  notify: pr_comment + slack_team
infra:
  action: rerun_on_new_runner
  notify: platform_oncall (if burst)
novel:
  action: block_merge
  notify: page_deploying_team
  require: human_label_within_24h
typescript
export async function handleFailure(label: Label, job: Job) {
  switch (label) {
    case "flake":
      if (job.reruns < 1) return rerun(job);
      return quarantineTest(job.testId, { slaHours: 72 });
    case "regression":
      return blockMerge(job.pr, `regression_fingerprint=${job.fp}`);
    case "infra":
      return rerun(job, { runnerLabel: "fresh" });
    case "novel":
      await page(job.deployingTeam, job);
      return blockMerge(job.pr, `novel_fingerprint=${job.fp}`);
  }
}
Quarantine with a hard fix SLA — do not let flakes rot coverage forever.

Feedback loop so the classifier stays honest

Signal Use
Human relabel in Slack Training gold
Rerun then green without code change Evidence for flake
Rerun then same fingerprint Evidence for regression
Post-merge incident tied to ignored fingerprint Severity bump
Track precision/recall weekly; alert if flake auto-rerun rate spikes (might be mislabeling regressions).

Closing checklist

  • [ ] Fingerprints from test id + normalized message + top stack frames
  • [ ] Labels include explicit novel; unknown ≠ flake
  • [ ] Auto-rerun only for flake (budget 1) and controlled infra
  • [ ] regression and novel block merge and notify humans
  • [ ] Quarantine SLA for repeated flakes; metrics on misclassification
  • [ ] Post-deploy novel signatures page the deploying team

Related reading

Last updated on September 11, 2026

Related: Day 55: Swarm Failure Modes · Lambda Timeouts, Retries, and DLQs: Idempotent Failure Handling · Lambda Multi-Arch Builds: arm64 CI Matrices That Actually Ship

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a comment

No account needed. Name and email are optional.