← All articles

Shift left became run everything on every commit

August 7, 2026 · Wilson Santos
On this page

"Shift left" started as an observation about cost: a defect found while you are writing the code is cheaper to fix than one found in staging, which is cheaper than one found by a customer. That is true, uncontroversial, and useful.

What got built from it, in a lot of places, is a pipeline that runs every scanner anyone could think of on every push, takes eleven minutes, and produces four hundred findings that nobody reads.

Then somebody adds continue-on-error so it stops blocking merges, and the whole thing becomes decorative.

What went wrong

The idea

When feedback arrives. A defect found while you are still writing the code is cheaper than one found in staging, which is cheaper than one a customer finds.

What got built

Where the tool runs. The same scanners, moved earlier in the file, on every push. Nothing about the arrival time changed except that it now blocks a merge.

Those are not the same, and conflating them produces the specific failure above.

Moving a scanner earlier does nothing on its own. It helps only if the feedback is:

  • fast enough that you are still thinking about the code, and
  • relevant enough that acting on it is obviously worth the interruption.

Miss either and you have not shifted anything left. You have added latency to a merge and a report to ignore.

Latency is a threshold, not a gradient

There is a real discontinuity in how people respond to feedback speed, and it is worth designing around rather than optimizing toward.

What a developer does, by how long the check took These are bands of behavior rather than a smooth curve. Under a minute the code is still in mind and the fix happens immediately. Between one and five minutes the developer finishes what they moved on to first. Past five minutes the finding is triaged onto a list. Past ten the check is routed around, because the alternative is not shipping. HOW LONG THE CHECK TAKES You fix it now The code is still in your head, so fixing beats coming back. You finish first You have started something else and will return to it. It goes on a list Triaged rather than fixed, and lists are where findings wait. People route around Not out of malice. The alternative is not shipping. under a minute1 to 5 minutes past 5 minutespast 10 minutes Four bands, not a slope. Shaving a nine-minute check to seven moves nothing across a boundary.
The design question is not how early a check runs but what fits in the first band, which is a much smaller set than everything and worth choosing on purpose.

Under about a minute, feedback lands while you still have the code in your head. You fix it now, because it is cheaper than context-switching back.

Past five minutes, you have moved on. The finding arrives as an interruption to whatever you are doing next, and gets triaged rather than fixed, which means it goes on a list, and lists are where findings go to wait.

Past ten, people route around it. Not out of malice, because the alternative is not shipping.

So the design question is not "how early" but "what fits under a minute". That is a much smaller set than "everything", and it is a set worth choosing deliberately.

What belongs early

The checks that are fast, precise and about the diff:

Fits in the first bandWhy
Secret detectionfast, near-zero false positives, and the one finding that gets categorically worse with time. A committed credential is exposed from the moment it is pushed
Static analysis on changed filesthe diff, not the repository
Dependency checks when the manifest changedand not otherwise. Nothing about your lockfile changed on a CSS commit
Anything that fails deterministicallya check whose answer varies run to run teaches people to re-run rather than to read

What does not fit: full dynamic scans against a deployed environment, benchmark suites needing a live cluster, anything image-based that has to pull and unpack layers. Those are valuable and they are not sixty-second checks. They belong on a schedule, or on a release, where their cost is proportionate.

The other half: only new things

Even a fast check becomes noise if it reports the same two hundred pre-existing findings on every pull request. Nobody inherited that backlog deliberately, and blocking every change on it guarantees the gate gets disabled.

Gating on what the change introduced is the only version of this that is fair to the person it stops. A gate reading the whole backlog asks a developer to answer for code they did not write, before they are allowed to ship code they did. Nobody agrees to that twice.

What the author is answerable for A pull request changes three files and introduces two findings into a repository that already has 247 open. A gate reading every open finding fails the change and asks its author to account for all 249. A gate reading only the difference fails the same change and asks them to account for the two they introduced. One pull request, three files it introduced 2 findings · 247 were already open A GATE ON EVERYTHING OPEN 249 findings between them and a merge Answerable for 247 they did not write A GATE ON THE DIFFERENCE 2 findings between them and a merge Answerable for 2 they did Both gates fail this pull request. Only one of them is asking its author a question they can answer.
The 247 do not disappear. They stay in the report, on the branch that owns them, where somebody can plan the work instead of discovering it at the moment they are trying to merge something else.

The feedback belongs on the pull request

A verdict a developer has to go and look for is a verdict they read once, in a log, after the build broke. Draugr posts the delta as a comment on the pull request itself, and treats that as a first-class output rather than an integration somebody bolts on.

One comment, edited in place

A sticky note per pull request, rewritten on each push rather than stacking up, so the thread stays readable after the fifth commit and the current answer is always the one at the top.

What to do, not what is wrong

The comment can carry actions rather than findings, so a dependency bump introducing forty shows as one upgrade. Set once in a template, because which shape a team wants is a property of how they review.

--publish picks the right one of GitHub, GitLab or Azure DevOps out of the CI environment, and on a pull request that is not the only place the answer lands: the new findings go to code scanning as well, where they annotate the lines that caused them.

None of it needs wiring. On GitHub the action's mode: auto scans both sides and posts the comment; on GitLab the template does the same with the differential gate on by default at P1. Gating on what a change introduced covers the outcomes of a diff and which one the gate reads.

The honest version

Shift left is not "run security tooling early". It is:

Give a developer feedback fast enough and specific enough that fixing it is the path of least resistance.

Everything else follows. If the feedback is slow, they will not fix it. If it is about somebody else's code, they cannot. If it is about two hundred things at once, they will not start. And if it can be bypassed, it eventually will be, so the question is whether it earns its place, not whether it is mandatory.

A gate that people route around is worse than no gate, because it produces the paperwork of security without the effect.

Draugr's approach is a single descriptor deciding what applies, a gate that reads only what a change introduced, and a ranking that puts the internet-facing critical above the internal medium, so the short list is short for a reason. The shift-left guide covers the idea, gating on what a change introduced covers the mechanism, and when everything is critical, nothing is covers the ranking half.