← All posts

We scanned 1,939 repositories. 95% of projects publishing an OpenAPI spec have no compatibility gate.

We scanned 1,939 GitHub repositories. Of the 85 with a real first-party OpenAPI spec, 81 had no breaking-change check in CI. Method, data, and corrections.

SpecShield ·

We went looking for projects that publish an OpenAPI specification and then let anything through CI unchecked. We expected a niche. We found the default.

Of 85 repositories with a genuine first-party OpenAPI spec, exactly four run any breaking-change detection at all. That is 95.3% with no compatibility gate — and one of the four is oasdiff's own repository, so among projects that merely use such a tool rather than build one, it is three.

Then we did the part nobody had done: we ran a differ across each project's own release history to see what they had actually shipped. That produced a second finding that undercuts the first, and we think it is the more useful one.

The funnel

Stage Method Result
1 · Harvest GitHub issue-search queries run over several rounds — breaking api change in:title, backwards incompatible api in:title, undocumented breaking change in:title, broke our integration in:title, and others ~2,800 raw hits
2 · De-noise Require ≥2 comments on the issue, removing a large block of auto-generated issue spam 1,939 repos
3 · Product fit Full git tree per repo; require a first-party OpenAPI/Swagger spec, excluding vendor/, node_modules/, testdata/, fixtures, .github/, examples/, third_party/, generated/ 85 repos
4 · Tooling check Read every workflow file for oasdiff, optic, openapi-diff, pactflow, pact-broker, can-i-deploy, bump-cli, openapi-changes 4 had any

Of those 85, three more run OpenAPI linting — Spectral or Redocly — but linting checks whether a spec is well-formed, not whether it broke someone. A spec can be immaculate and still delete the endpoint your customers call.

The four with real detection all use oasdiff. Not one used anything else.

Then we looked at what they actually shipped

An absence of tooling is only interesting if it has consequences. So for each project we fetched its spec at up to ten consecutive releases and diffed each pair, classifying every change by whether a consumer's code would actually break.

Across the 60 repositories with usable release history: 2,118 breaking changes, 631 of them consumer-visible — removed endpoints, removed parameters, enum values dropped, request fields made mandatory.

The largest single case was a project that renamed its entire core resource — every /v1/items* route replaced by /v1/entities* — in one release. Fourteen paths removed, twenty-three added. Any integration calling the old routes gets a 404.

The counter-finding, which matters more

Here is where our own thesis took a hit.

Of the 18 repositories that shipped a consumer-visible breaking change, 12 disclosed every one of them — 67%.

And they disclosed well. The project that renamed /v1/items to /v1/entities put this in its release notes, in a callout, addressed to exactly the right people:

[!IMPORTANT] If you are an integration/software developer, the /v1/items* and /v1/locations* endpoints have been entirely replaced by /v1/entities*. Please review our entity merge documentation for the API changes.

A linked migration guide. A backup warning. Better practice than most commercial API vendors manage.

So the story we set out to tell — "projects with no tooling are silently breaking their consumers" — is not what the data says. Most of them are breaking consumers loudly, in the changelog, on purpose. The tooling gap is real. The silence we assumed would accompany it mostly isn't there.

Fourteen releases across the whole set shipped a consumer-visible break with written notes that never mentioned it. Fourteen, across 1,939 repositories scanned. That is a real problem and a much smaller one than the headline implies.

Four ways we were wrong before we were right

We are publishing the corrections because a statistic whose author never shows their errors is a statistic you cannot check. Every one of these inflated our numbers in our own favour, and we found them by reading output instead of trusting counts.

1. Version order is not time order

Projects backport. A monorepo tags several components in parallel — core/v1.7.14 and ent-v1.5.13-base on the same day. Sorting releases by date walks versions backwards, and every addition is then reported as a removal.

One project appeared to have shipped 577 breaking changes. Restricting the comparison to a single coherent release line, ordered by version, brought it to 249. Across the whole set, fixing the ordering took the total from 4,285 to 2,606 breaking changes, and consumer-visible ones from 1,614 to 813.

Roughly half of our original headline was a sorting bug.

2. Parallel maintenance lines

Even within one tag prefix, version order lies. One project ships v0.38.26 in 2026, long after v1.0.0 in 2024, because 0.38 is a maintained LTS line. Diffing v0.40.0 against v1.0.0 compares two branches, not an upgrade anyone travelled.

We now discard any pair whose "newer" release predates the older one. That removed 17 comparisons and, in one case, 100% of a repository's findings — a project we had ranked among our strongest examples turned out to have shipped nothing breaking at all.

3. Direction matters

A request field becoming required breaks every client that omits it. A response field becoming required breaks nobody — the server is promising more, not demanding it. We counted both as consumer-breaking. Twenty-five changes were miscounted this way, taking consumer-visible changes from 813 to 788.

4. "Undocumented" was a vocabulary problem

Our detector searched release notes for breaking, backwards-incompatible, no longer, removed the, migration guide. Teams write "entirely replaced by", "moved to", "renamed", "Upgrading", or simply put it in a [!IMPORTANT] block.

We also read only the first 6,000 characters of each release body, and truncated one disclosure in a body of roughly 8,400. And we read only GitHub release bodies — one project's releases are two links to its own docs site, where every version has a dedicated Breaking Changes section. Calling that "undocumented" would have accused the most rigorous team in the sample of hiding things.

Undisclosed releases: 48 → 23 → 14 as each of those was fixed. Two thirds of our central claim was wrong.

And the headline itself

Our first draft of this post said 97%. The actual figure is 95.3% — 81 of 85. The 97% came from a denominator that never got re-checked after the sample changed. We caught it while writing this paragraph.

What we would now say the problem is

Not "teams don't know they're breaking consumers." Most of the ones we could measure do know, and say so.

The sharper finding sits with the teams that did adopt detection. In a separate survey we ran in August — 504 repositories already running oasdiff, a different and non-overlapping sample from the 1,939 here — we read the workflow files of eight of them. Six had deliberately made the check non-blocking — continue-on-error: true, or the command piped through || true. One wrote down why:

The baseline used to be live production (app.infisical.com). Production tracks the last deploy rather than main, so every breaking change legitimately merged to main re-fired on every open PR until the next deploy. Unrelated PRs failed with identical errors and the check stopped being believed.

This check is advisory. It reports what the PR changes and comments when something breaks, but it never fails the job. Making it blocking is a separate decision, and one worth taking only once the reports have been observed to be trustworthy.

That is the real shape of it. Detection is solved and free. The hard part is a gate people still trust six months in — and the honest answer is that most teams who tried one turned it off.

Caveats

  • Sample bias. Stage 1 selected repositories where somebody had already filed a breaking-change complaint. This is not a random sample of OpenAPI publishers; if anything it over-represents projects that have felt the problem.
  • Public repos only. Everything here is open source. Internal APIs behind a company firewall may look nothing like this.
  • Ten releases per project. Longer histories were truncated, so the breaking-change totals are a floor, not a ceiling.
  • Spec ≠ API. A change flagged here may be intentional, communicated elsewhere, or on an endpoint nobody consumes.
  • 10 of 76 "specs" weren't OpenAPI documents at all — files whose names contain openapi or swagger but which are JSON Schema or config. Our first filter matched on filename; reading them fixed it.
  • Counts drift. Repositories kept releasing while we measured.

The data

The full dataset is published so you can check any of this:

breaking-change-issue-survey-2026-09.json (700 KB) — the 1,939 repositories behind the headline, with every issue title, URL and comment count.

openapi-compatibility-gate-survey-2026-09.json (424 KB) — the 76 repositories whose spec history we diffed.

The second file contains all 76 repositories, the spec path we read, every release pair compared with its dates, whether the pair was discarded as cross-branch, each release's disclosure classification, and every consumer-visible change with its type, method, path and tier. The method and the classification rules are in each file, so they stand on their own.

One honest gap, noted in the first file: the harvest ran over several rounds and only 924 of 2,301 issues retained a label for which query surfaced them, covering 12 distinct searches. The repositories, titles and counts are complete; the query attribution is not.

If you reproduce it and get a different number, we would rather hear about it than not.


We build SpecShield, which is a breaking-change gate, so we had every incentive to report that number as bad as possible. It is in our interest for the answer to be "95% of projects are flying blind." The data says most of them are not flying blind at all — they are telling their users exactly what they broke. We would rather publish that than the version that sells better.