Lesson 19 of 30
A red pipeline nobody sees is just a slower failure. Real observability means a human gets pinged fast, and flaky tests get tracked instead of just re-run.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A pipeline that fails silently isn't actually protecting anything — it's just delaying the bad news until someone happens to check, usually a reviewer wondering why their PR is stuck, or worse, a user hitting a bug that a red build would have caught hours earlier. Notifications close that gap: the pipeline tells a human, fast, through a channel they already watch, the moment something breaks.
But notifications alone only solve "did I find out." Observability is the bigger habit: treating your CI history as data, not just a pass/fail gate you glance at and forget. The clearest example is flaky tests — a test that fails maybe one run in twenty, for no code-related reason. The lazy response is "just re-run the job," and most teams do exactly that, click by click, for months. The observable response is to notice that the same test has now failed on unrelated PRs eleven times this quarter, quarantine it, and actually fix or delete it — because a flaky test that gets re-run instead of fixed doesn't just waste minutes, it trains the whole team to distrust red builds, which is the exact opposite of what a pipeline is for.
This repo's own pipeline (.github/workflows/ci.yml) doesn't currently notify anyone — it runs type-check, lint, tests, content validation, and build, and stops there. Adding a failure notification is a small addition to the same job:
- name: Production build
run: pnpm build
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
channel-id: "#ci-alerts"
slack-message: |
:red_circle: CI failed on `${{ github.ref_name }}`
Commit: ${{ github.sha }}
By: ${{ github.actor }}
<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
if: failure() is doing the real work here — it means this step only runs when a previous step in the job failed, so a healthy pipeline stays quiet and only a broken one pings the channel. That's the difference between a notification and noise: alert on the state change, not on every run.
Tracking flakiness instead of just re-running is a separate habit, and it needs a place to write to — a step that appends every test's pass/fail/duration to a dashboard or a simple database table (test_name, run_id, outcome, timestamp) turns "this test feels flaky" into "this test has an 8% failure rate over the last 50 runs, and it's always the same assertion," which is something you can actually act on.
Earlier lessons treated tests and lint as gates that stop bad code from merging. Notifications and flaky-test tracking are what turn that gate into a feedback loop the team actually learns from over time — the difference between a pipeline that blocks one bad PR and a pipeline that gets more trustworthy every month because someone is watching its own failure patterns, not just its instant-by-instant verdict.