A Green Pipeline Is A Narrower Claim Than It Looks

A green tick tells you exactly one thing: the specific steps configured to run, ran, and none of them objected. It does not tell you that the change works. Those two statements feel interchangeable until the day they aren't, and I watched that day play out this week in a small UI change that looked completely uncontroversial — hide a dropdown that shouldn't have been there — and still managed to ship a broken screen with every visible signal saying otherwise.
The change itself was almost too small to be interesting. Someone needed to remove a permission-level control from a shared header, because it had been added for an earlier version of the product and no longer belonged there. The obvious, fast way to hide something you're not ready to fully delete is to comment out the block it lives in. That's what happened. Except the block being commented out wasn't just the dropdown — it was the entire wrapper around it, which also happened to contain the user's avatar and name. Removing one small thing took two other things with it that nobody meant to touch. The commit did exactly what it was told to do. It just wasn't told precisely enough.
That alone is a familiar shape of mistake: the blast radius of an edit is usually larger than the thing you're picturing when you make it. Commenting out a block is a blunt instrument dressed up as a scalpel. But what made this worth writing about wasn't the mistake — mistakes like that happen constantly and get caught constantly. It was why the automated checks didn't catch it, and that reason had two separate, stackable causes, each innocuous on its own.
The first cause: the project had a step whose entire job was to validate a browser-facing prototype file, and it passed. But "validate" in this case meant running the inline script through a bare syntax parser — enough to prove the JavaScript was well-formed, nothing more. Well-formed code with a runtime bug in it parses perfectly. The commented-out dropdown left behind an event-listener binding pointed at an element that no longer existed. Nothing about that is a syntax error. It's a null reference that only announces itself the moment a browser actually tries to execute the line, which a parser, by design, never does. The check had a name that implied verification and a behaviour that delivered something much narrower: confirmation that the file could be read, not that it could be run.
The second cause is the one I found more interesting, because it's less about the code and more about the shape of the pipeline itself. The job that would have caught the real defect — an actual build-and-test step — was configured without any rule restricting when it should run, which in that particular CI system means it's excluded from merge-request pipelines entirely and only fires on a direct push to a branch. So the checkmark sitting on the merge request, the one a reviewer glances at before approving, never included that job at all. It was green because the only things it was allowed to run were green. The job that mattered was running somewhere else, on a different trigger, and unless you knew to go looking for the branch's own pipeline instead of the merge request's summary, you'd never see it — pass or fail.
Stack those two gaps and you get a change that commented out more than intended, broke rendering, threw a runtime error the moment the page loaded, and still produced every outward sign of health: green request, green build. The only way anyone actually found the problem was the least glamorous possible method — someone opened the file in a real browser and looked at what happened. Not because the process demanded it, but because a person on the team treated "the pipeline is green" as a claim to be checked rather than a fact to be trusted, went looking anyway, and traced the failure back through both gaps at once.
I think this is worth more attention than "write better tests," because the failure here wasn't a missing test. There was a test. It ran. It passed. The gap was between what the check's name implied and what its implementation actually covered, compounded by a second gap between what the badge on the merge request implied and which jobs actually contributed to it. Both gaps are invisible unless you go and read the configuration, and almost nobody reads pipeline configuration once it's been working for a while. It becomes furniture. You stop asking what it's checking and start trusting the colour.
The practical habit I'd take from this is to occasionally ask, of any check you rely on, two separate questions rather than one. Not just "does this pass," but "what would have to be true for this to fail, and does that class of bug fall inside what it's actually capable of detecting." A syntax check cannot detect a runtime error, no matter how many times it runs, and a job excluded from an event can't tell you anything about changes evaluated under that event, no matter how green its history looks elsewhere. Neither gap is a scandal. Both are completely ordinary configuration decisions, made for sensible reasons, that quietly narrowed what "passing" meant without anyone deciding to narrow it on purpose.
The other thing worth keeping is smaller and more human: the person who found this didn't find it by suspicion of a colleague's work, and the review comment that followed didn't read like a correction. It read like a thank-you for catching something that would have bitten the next person to touch the file. That's not a small detail. The technical fix here was two lines of restored markup and a deleted event listener. The reason it got fixed at all was someone deciding that a green pipeline deserved a second look, and someone else being glad, rather than defensive, that it got one.


Share your thoughts