How should we manage breaking changes?

Many people have voiced their dissatisfaction with how breaking are handled in nixpkgs. Theres been recent discussion about a breaking change in PAM. While discussion about that particular breaking change is ongoing, I have created this topic to focus on breaking changes more generally, to provide a place to propose alternatives, and to gauge interest in tackling this problem through more official channels.

About me personally:
I am not a nixpkgs contributor and I do not know how RFCs or working groups are made. I do maintain a small flake module that a few other people consume.
I left the original post vague because I genuinely don’t know how these things work. But, if making a few posts gets the ball rolling I’m happy to help out.

I’ve been a fan of Evolution of Public Contracts from the C4 already.

We stopped breaking public contracts simply by banning the practice.

The issue I’ve seen so far is that we tend to underspecify what counts as breaking, and who the “public” is. Who’s the user? Is it nixpkgs internal contributors (where I’ve seen the most focus be), users of the module system, users of individual packages (maybe not on NixOS), or consumers of a nixpkgs source itself? I’ve seen (and both caused, and fixed) breakage of all four before.

Note that the C4 also explains:

All Public Contracts (APIs or protocols) SHALL be documented.

I think this is a potential good use for the RFC process, though pushing an RFC through also doesn’t account for organic growth and people already implicitly depending on certain behavior. So “some important docs that aren’t a RFC” may be the happy medium.

Regardless, fewer breaking changes are a very good thing, and we should preferably only break relied on behavior if absolutely necessary.

(That being said, sometimes we do need to take a chainsaw to certain API surfaces, and maybe if there’s agreement, that’s okay too.)

2 Likes

I think whatever comes of this, it’s important to focus on how we balance user experience with contributor experience. Change management can be quite a chore and I don’t want to further burden nixpkgs maintainers.

Some things I’ve seen suggested:

  • Some sort of versioning
  • Arch Linux style breaking changes lists on updates (channels or flake update)
  • Mailing list / github action to notify people who subscribe about breaking changes in modules they’re interested in
  • adding/changing release channels
  • only make breaking changes around release windows.

A few of my own observations:

  • breaking changes that happen during runtime are much worse than expressions that don’t evaluate anymore.
  • people usually use unstable to get the newest packages, not the newest modules
  • using unstable is a common practice, not just something people use for testing.

I generally agree with software that you can tightly control. But given that modules are often downstream of packages, I don’t think it’s really feasible to never break an interface in nixpkgs. Upstream app developers are going to add breaking changes to their config, and I think it’s reasonable to expect the nix module for that package to also have a breaking change if it’s significant. Imagine something like your favorite modal editor changing its config language fro lua to lisp; there’s no reasonable way prevent the nix module for that editor from also having breaking changes.

Some good questions to ask:

  • What behavior is relied on explicitly (say, being able to parse your config)?
  • What behavior is relied on implicitly (like the PAM options working, or maintainers on Repology being correct)?
  • What implicit behavior should we be careful to preserve with backwards compatibility?
  • What behavior can we afford to break without annoying users?
  • What behavior can we afford to break even if we temporarily annoy users, because it’s for the “greater good?”
1 Like