---
title: "What GHSA-mwp4-54f8-5fhr kept doing to a client's main branch every morning"
canonical: https://dxdev.com/blog/per-message-alert-for-a-per-machine-failure/
datePublished: 2026-08-12
---
## Three red runs, one name

I ran `gh run list` on a client's API repo expecting one failure and found a pattern. `dependency-audit` failed on 2026-08-04 at 09:24 UTC, on schedule, on main. It failed again on 2026-08-05 at 09:22 UTC, same workflow, same branch, same trigger. In between, the same workflow had gone green on pushes to `develop` and on a manual dispatch from a feature branch.

The same problem was being reported over and over, from slightly different angles, and nobody had looked at it long enough to name it. The fix needed no code change. The alarm was built on the wrong unit, and seeing that was the fix.

## What the alarm looked like

`dependency-audit` is still filed as `npm-audit.yml` on disk, because branch protection rules match on filename and nobody wanted to touch that. It runs on three triggers: every push to `main`, a release branch, and `develop`, plus a daily cron at 8:27 UTC so a freshly disclosed CVE pages someone even on a day with no pushes.

That is a reasonable design for catching new vulnerabilities fast. But the same unresolved issue fails the build once per push per branch, and once per day forever, until someone closes it. The alarm fires once per event. The problem exists once per package.

And when it fires, you get a wall of table output. Package name, vulnerable range, patched range, a dependency path, an advisory link, repeated for every finding and dumped fresh on every run. Nothing in it says "this is the same thing you saw yesterday" or "here's who owns this." You have to read the `Paths` row yourself to find the culprit: `apps__api > express-rate-limit > ip-address`, vulnerable at `<=10.3.0`, patched at `>=10.3.1`, filed under `GHSA-mwp4-54f8-5fhr`. That one fact was buried in a report that treated every run as a fresh incident instead of a status check on a known one.

## Giving the problem a home outside CI

The repo already had the mechanism: a documented exceptions list, `docs/security-audit-exceptions.md`. An accepted CVE gets an owner, a review date and a removal condition there, because `package.json` doesn't take comments and someone has to be able to say why a known-red audit is intentional. That moves the alarm from one per event to one record per problem. A CVE that has been triaged and accepted stops being news every time a branch touches the audit step. It becomes news again only when the removal condition is met or the review date passes.

When the patch landed, the record proved its worth from the other direction. The commit that shipped it says what happened in its own message: two new production highs, and a backport that made an existing exception obsolete. That is the alarm doing its job in one place. Not three branches independently telling us the same thing, and not a daily cron re-announcing a fact that hadn't changed since yesterday. One entry, updated once, when the underlying thing changed.

## Why this generalizes

Every alert system has an implicit unit: what counts as one thing worth telling you about. If that unit is smaller than the thing that's broken, you get exactly this. The same failure is multiplied across every branch, every push and every scheduled tick, dressed up as separate news each time. Three failing runs feels like three problems. It was one package, one advisory, one fix.

A report that dumps raw data also isn't a message. A table of vulnerable versions says less than a sentence that names what's wrong and who is supposed to act on it. If we have to read the third column of a table to find the one fact that matters, the alert hasn't told us anything yet. It has handed us the homework.

The `GHSA-mwp4-54f8-5fhr` advisory on `express-rate-limit` sat in that exceptions file for exactly as long as it took to matter. Once a backport closed it, the fix commit said so in one line, and every future run of `dependency-audit` goes quiet on that line instead of relitigating it. It cost nothing but a markdown file nobody wanted to touch.

Check whether your alerts fire once per event or once per problem. When one known issue fails the build on every push, branch and scheduled run, give it a single record with an owner, a review date and a removal condition.
