CASE STUDY · JULY 16, 2026 · 6 MIN

A breaking-changes feed for your stack

I run a service on FastAPI, SQLAlchemy, Stripe, pandas, and scikit-learn. I don't want a notification every time one of them tags a release — most are bug fixes and new features I'll pick up whenever. I want to hear about the things that will break my code on upgrade: removals, deprecations, required migrations. So I built one feed that reads all five libraries' GitHub releases and emails me a daily digest of only that. Here's what it took — including the two things I got wrong first.

The build

A feed is a set of sources plus a plain-language filter. The sources are the libraries' GitHub release feeds (every repo publishes one at /releases.atom). The filter is a sentence describing what I actually care about:

Breaking changes, deprecations, removals, and required migration steps in
these libraries. Things that would break a running FastAPI/SQLAlchemy/
Stripe/pandas service or require code changes on upgrade. NOT new features,
performance improvements, docs, typo fixes, or routine dependency bumps.
curl -sX POST https://api.rssemit.com/v1/watchers \
  -H "Authorization: Bearer $EMIT_KEY" \
  -d '{
    "prompt": "Breaking changes, deprecations, removals, and required
      migration steps ... NOT new features, docs, or routine bumps.",
    "sources": [
      "https://github.com/fastapi/fastapi/releases.atom",
      "https://github.com/sqlalchemy/sqlalchemy/releases.atom",
      "https://github.com/stripe/stripe-python/releases.atom",
      "https://github.com/pandas-dev/pandas/releases.atom",
      "https://github.com/scikit-learn/scikit-learn/releases.atom"
    ],
    "discover": false,
    "cadence": "daily"
  }'

Every new release gets read by an LLM and scored against that sentence. Ones that clear the threshold are emitted to the feed; the rest are kept with a one-line reason, so I can always see why something didn't make it. The feed is a real Atom/JSON URL — I piped it to email, but it works in any reader too.

Mistake one: I let it discover its own sources

My first version left discover: false off. emit can search for sources matching a prompt, which is the right default when you don't already know where to look — but I did. On top of my five hand-picked changelogs, discovery added a source of its own, and it was a Spanish-language Flutter tutorial blog. It contributed dozens of items about StatefulWidget animations that then crowded the real release notes out of the feed's preview window entirely.

The lesson is simple: naming your sources is not a request for more of them. If you know the five changelogs you want, pass them and set discover: false. Discovery is for the "I want a feed about X and don't know the good sources" case, not this one.

Mistake two: I set the filter too strict, and it emitted nothing

The filter has three strictness tiers — loose, balanced, strict. I started on strict, figuring a breaking-changes feed should be conservative. It ran for a day and emitted zero items. That's the failure mode you don't notice: a feed that's quietly too strict looks exactly like a feed with nothing to report.

So I measured it. The filter scores each item on a five-level scale — none, weak, partial, strong, exact — and strict only admits the top two. Real dependency releases kept landing at partial: genuinely relevant, arguably worth seeing, but hedged ("bug fixes, one removed parameter"). Strict dropped every one of them. On balanced, those partials come through. I moved the feed to balanced, and within a poll it emitted its first real item:

stripe-python v15.4.0a4"Explicit removals of a resource and several methods, plus removed param support requiring code changes."

That is exactly the feed's job: a released version that will break code on upgrade, pulled out of a stream that was otherwise routine. If your filtered feed is silent, don't assume there's nothing to say — check whether the threshold is eating it.

The thing that surprised me: breaking changes hide at the bottom

Release notes don't lead with the scary part. They lead with highlights and new features, and the "Breaking changes" section is often near the end — I measured one FastAPI release that buried its first removal past the 8,000th character. A filter that only reads the first paragraph will read a breaking release as routine and drop it. So the judge reads the whole note, not a snippet. You pay for the extra text it reads (metering scales with length, so a long changelog costs a little more than a one-line tag), but a missed breaking change is the one thing this feed can't afford.

What the digest looks like

Once a day, if anything crossed the bar, one email. GitHub titles release entries by version alone, so a raw digest subject would read v15.4.0a4 and tell me nothing — emit prefixes the source, so it arrives as stripe-python v15.4.0a4 with the model's one-line reason underneath. If nothing broke, no email. Most days, no email — which is the point.

Build your own

Swap in the libraries you actually run. Any repo's releases feed is github.com/<org>/<repo>/releases.atom. Keep discover off if you're naming sources, start on balanced, and read the per-item reasons for a few days to see whether the prompt matches your idea of "breaking." If you use an agent, the whole thing is one instruction:

Using emit: create a daily feed named "Dependency drift" with these sources
and discover off — the releases.atom URLs for fastapi, sqlalchemy,
stripe-python, pandas and scikit-learn — filtering for breaking changes,
deprecations, removals and required migrations, not features or docs. Use
the balanced threshold, then pipe it to email at posts@mydomain.dev.
Build a breaking-changes feed → How filtered feeds work