Framework and runtime release notes: shipping a version an ecosystem has to absorb

A framework, language, or runtime release has a property that almost no application release shares: the people who want your new version mostly cannot take it yet. An app developer reading your v5 announcement checks their lockfile, finds forty packages built against v4, and closes the tab. They are not being lazy — they are blocked, and they stay blocked until the authors of those forty packages upgrade first. Node majors, React majors, Rails majors, Python minors: every one of them lands this way, in dependency order, over months.

That single fact should reorganize how you write the release notes. You are not announcing one release to one audience. You are running a relay: first the ecosystem’s library and plugin authors absorb the change, then — and only then — application developers can. Notes that speak only to the second group on day one are addressed to people who can’t act on them yet, and silent about the people who can.

Adoption happens in dependency order

Picture the dependency graph of your ecosystem with your framework at the root. An application at the leaf can upgrade only when every path between it and you is compatible with the new version. One unmaintained plugin three levels down pins the whole app. This has two consequences for your writing:

  • The slowest dependency sets the pace, so your leverage is at the top of the graph. A week of a widely-used library author’s time unblocks thousands of apps. Your early notes should spend their best material on that person — the exact API removals, the native/ABI changes, the peer-dependency semantics — not on the app-facing highlights reel.
  • “Available” and “adoptable” are different dates, and your notes should acknowledge the gap. The honest GA announcement says who can move now (new projects, apps with shallow dependency trees) and who should wait — and gives the waiters a way to watch progress rather than a vague “soon”.

Two documents: the release post and the upgrade guide

Majors need both, and they do different jobs. The release post (your changelog entry) answers “what changed and why should I care?” — headline features, the compatibility one-liner, support-window changes, links out. The upgrade guide answers “what exactly do I do?” — ordered steps, every breaking change with before/after code, codemod commands, troubleshooting. The docs-vs-changelog rule applies: the entry is the dated announcement, the guide is the living document you keep fixing as upgrade reports come in; never paste one into the other, always link.

The release post’s compatibility one-liner deserves real care. Frameworks are chosen dependencies, so the library-notes discipline applies: state up front whether a typical app upgrades clean. “Most apps upgrade with no changes; the removals below affect direct users of the internals API” is the single most-wanted sentence in any framework release post, and the least often written.

The RC is a release for library authors

Release candidates get treated as “GA, but shy” — same notes, softer promises. That wastes the RC’s whole purpose. In an ecosystem, the RC phase is when library authors port, so RC notes are a release for them, with their own content:

  • The complete breaking list, final, up front. Library authors port against the RC on the assumption it is the real interface. Publishing the full removals-and-changes list at RC — not trickling it out — is what makes the assumption safe.
  • What “compatible” means mechanically: the peer-dependency range to declare, the compat matrix to test against, whether one package version can support both majors at once (and the pattern to use if so). Every ecosystem that has done this well — dual-publishing, compat shims, feature detection — did it because the framework spelled the pattern out.
  • An explicit invitation with a channel. “If you maintain a package that touches X, test against the RC and report here” turns passive readers into your porting workforce. Per the beta-notes loop, credit the people who file the reports.

The stage labels carry unusual weight here: “RC” is a promise to strangers that porting work done now survives to GA. Which leads to the rule ecosystems learn the hard way:

Nothing gets renamed between RC and GA

During the RC window, the ecosystem writes your documentation for you: migration blog posts, conference talks, tutorial updates, answers on every Q&A site, and — increasingly — the training corpora of coding assistants. Rename an API between RC and GA and you invalidate all of it at once. The cost lands on people who did exactly what you asked (adopted early, wrote about it), and it compounds: for years, newcomers will paste RC-era snippets that don’t run, and nobody will know why.

Treat public names as frozen at RC. If you absolutely must rename — a trademark problem, a genuine design error — ship the old name as a deprecated alias through GA, and write the rename as a loud entry of its own (the rename playbook applies at API scale). A wart with ten thousand working tutorials beats a clean name with none.

Your major started shipping one version ago

The best framework majors are boring for people who kept up, because everything removed in v5 spent v4 shouting. The deprecation-policy mechanics are standard; what’s special in an ecosystem is where the warning appears: a runtime deprecation warning surfaces in the terminal of an app developer whose dependency called the old API. The app developer can’t fix it — only file an issue upstream. So make the warning carry the package boundary: name the calling module if you can detect it, and print a permalink to the deprecation entry so the resulting upstream issue starts with your explanation attached instead of a screenshot of a stack trace.

Then, in the major’s own notes, close the loop: “everything removed here was deprecated in v4.x; if you run v4 with no deprecation warnings, v5 has no breaking changes for you.” That sentence converts a scary major into a checklist item — and it’s only true if you actually held the line and removed nothing unannounced, per the breaking-changes playbook.

Codemods: automate the boring 80% (and say what’s left)

At ecosystem scale, a mechanical migration — rename this import, rewrite that call signature — is multiplied across millions of codebases, which is why serious frameworks now ship codemods with their majors. The design-system rule holds: the codemod command is the first line of the breaking change’s migration section, above the manual steps. But the ecosystem version of the rule has a second half: state the coverage honestly. “The codemod handles the import renames and the config format; it cannot migrate custom middleware — see the manual section” is what lets a tech lead estimate the upgrade. A codemod advertised as complete that leaves a build broken costs you the trust that makes anyone run the next one.

Tell app developers when not to upgrade yet

The bravest sentence in a framework GA post is “you may not want this yet.” It is also the most credibility you can buy per word. Practical forms of it:

  • An ecosystem readiness view: a tracking issue, a compatibility table, or a tag on your changelog where the status of the major ecosystem packages is visible. You are not responsible for porting them; you are the only party positioned to make the state legible.
  • A wait-signal in the entry itself: “if you depend heavily on X, wait for its v5-compatible release — track it here.”
  • New-project defaults as the adoption vanguard: the day your scaffolding tool defaults to the new major is itself a changelog entry; it is the moment the ecosystem’s newcomers switch sides.

None of this slows adoption. It routes it: the people who can move today do, the people who can’t know exactly what they’re waiting for, and nobody burns a week discovering the blocker themselves and blaming you.

Release lines, support windows, and backports

Once an ecosystem depends on you, you are maintaining several versions at once whether you admit it or not — better to admit it in public, per the LTS playbook: which lines exist, what each still receives, and the dated cliff where each ends. Runtimes have made even/odd trains and scheduled EOL dates a familiar convention; the convention only works because the calendar is published and kept.

The corollary for every security release: the backport table. One vulnerability, several supported lines — the entry must say which patch release fixes it on each line, because “fixed in 20.11.1, 18.19.1, and not in the EOL 16.x line” is the sentence an operator schedules a night around (advisory rules apply for the details). And when a major drops an OS, an architecture, or a minimum compiler, that’s a platform-drop entry in its own right — doubly so because dropping it in the runtime drops it for every app built on top, most of whose authors will hear it from you or not at all.

A release entry template

For the changelog entry itself (the upgrade guide is its own document):

v5.0.0 — <date>

<One paragraph: what this major is about, in outcomes.>

Most apps: upgrade via npx <framework>-codemod v5 — typically
<n> minutes. If you run v4 with no deprecation warnings, nothing
below breaks you.

Breaking (all deprecated since v4.2 — see the upgrade guide: <url>):
- <removal> — codemod handles it
- <behavior change> — manual, see guide §<n>

Library authors: peer range >=4.2 <6 supports both majors;
compat notes: <url>. Ecosystem status: <tracking url>.

Support: v5 is current; v4 gets fixes until <date>, security until
<date> (policy: <url>). New-project templates default to v5 on
<date>.

Six anti-patterns

  • The leaf-audience-only launch. A GA post all about app developer benefits, with nothing for the library authors who gate whether app developers can ever have them.
  • The RC rename. Any public API renamed between RC and GA. Every tutorial, answer, and assistant snippet written during your RC window is now a newcomer trap with your name on it.
  • The complete-sounding codemod. “Just run the codemod” over a migration that’s 60% automatable. The first broken build teaches people to hand-migrate forever after.
  • The invisible support cliff. Old lines quietly stop receiving fixes with no dated policy — discovered by whoever ships the next CVE report for a line you’d privately abandoned.
  • Deprecation warnings without addresses. Runtime warnings that name neither the calling package nor a permalink — generating thousands of confused issues filed against the wrong repo.
  • “Upgrade now” with a blocked ecosystem. Pushing app developers to a major their dependency trees can’t follow yet. They can’t comply, and the nagging reads as not knowing your own ecosystem.

Running framework release notes on Wakelog

The relay structure maps cleanly onto Wakelog: the RC announcement, the GA post, the templates-flipped entry, and each line’s security releases are separate dated entries with stable permalinks — the kind you can print from a runtime deprecation warning or paste into an upstream issue. Tags separate the streams (breaking for removals, security for backport tables, announcement for the campaign beats), and RSS + JSON feeds are what the ecosystem’s own CI tripwires and bots watch. Your release pipeline posts the entry with one curl at tag time; drafts plus scheduled publishing let you write the GA post during the freeze and fire it when the tag lands. The honest boundary: Wakelog won’t build your compat matrix or run your codemods — it’s where every step of the relay gets announced, dated, and linkable.

Start your changelog — free   Next: CLI & library release notes →

Related guides

Last updated 2026-08-02 · All guides