Somewhere along the way the word fork picked up a hostile ring, as if clicking that button were the opening move in a community schism instead of the cheapest way to make a dependency do one thing differently, and the folklore costs real time, because teams that need a one-line fix will contort themselves with wrapper layers, patch scripts, and vendored tarballs rather than be seen maintaining a fork, when the fork was the honest tool for the job from the start. Four of the repos under the visorcraft organization are forks of other people’s projects, none of them are hostile, and each one has a different relationship to its upstream, which is the part worth writing down, because the interesting question was never “should you fork” but “what do you do with the fork six months later.”
The policy, in three sentences
Our rule fits on an index card: anything that is generally useful goes upstream as a pull request, anything that is specific to our builds or our hardware stays pinned in the fork on a branch we rebase, and nothing is allowed to rot, because a fork you stop rebasing is just a snapshot with delusions of grandeur. The first clause keeps us honest as citizens, the second keeps us honest as engineers, and the third is the one that actually costs time, since rebasing four forks against moving upstreams is real work that shows up in nobody’s feature list.
SABnzbd: the loop working as designed
The clean case is SABnzbd, the Usenet downloader, where we hit a spurious “is not writable at all” warning that traced back to a non-atomic writability self-test, so the fix went into our fork first because we needed the noise gone, and then went upstream as a pull request, which the maintainers merged in June of this year. That is the whole lifecycle in one pass: we were never blocked waiting for anyone, the fix now belongs to everyone, and our fork goes back to being what it should be most of the time, a tracking mirror that exists in case we need it again. When people picture forking they imagine the acrimonious split, but this is the far more common shape, a temporary divergence measured in days.
OpenRazer: hardware enablement, upstream-first
OpenRazer is the driver and daemon that talks to Razer peripherals on Linux, and we needed it to know about the Razer Blade 16 (2026), model RZ09-0581, USB id 1532:02E0, because that is the laptop on our bench and a driver that does not recognize the keyboard is not much of a driver. The device support lives on our add-blade-16-2026 branch, the pull request to upstream is open and waiting its turn, and in the meantime we keep the branch merged current against upstream master so the divergence stays small and the branch never becomes archaeology. Hardware enablement is the friendliest possible fork reason, since the maintainer cannot test a laptop they do not own and the contribution is purely additive, but “friendly” does not mean “instant”, and the pinned branch is what lets us ship our own work while the review queue moves at the review queue’s pace.
cxx-qt: the declined PR and the deliberate pin
The cxx-qt fork is the instructive one, because it shows both remaining outcomes at once. LinSight and LinSync write their QObjects in Rust over cxx-qt, as we wrote about in the cxx-qt piece, and along the way we found that qt-build-utils was emitting a redundant qmldir alias into the generated qrc, so we sent that upstream as a pull request too, and the maintainers closed it unmerged, which is entirely their right and cost us nothing but a small carried patch. The other branch in that fork, windows_static, was never going to be a pull request at all, because it is a hack around Corrosion overriding RUSTFLAGS and dropping the flags from our cargo config.toml, which our Windows builds need so the C runtime links statically, and build plumbing that exists to satisfy one consumer’s packaging choices has no business cluttering a library everyone’s CI depends on. One branch is a declined contribution we keep rebasing, one branch is a pin we never offered, and both are the policy working, because the decision point was never “did upstream say yes” but “who is this change actually for.”
tail-tray: the fork that is just packaging
The fourth fork, tail-tray, is a Tailscale tray menu for KDE Plasma, and our copy exists for the least glamorous reason on this list: we added Ubuntu 26.04 to the CI matrix and bumped the version so fresh packages publish for the LTS builds our own machines run. There is no behavior change to offer upstream, no bug being fixed, just distribution plumbing for our install base, and sending someone a pull request full of our build matrix would be noise in their inbox, so it stays in the fork and the fork stays rebased, and that is the entire story, which is exactly how unremarkable a packaging fork should be.
What a fork costs, and why the list is four and not forty
The honest ledger has one big entry on it: every fork you carry is a rebase obligation that renews itself each time upstream moves, and the number of forks a small company can carry is bounded by the number of people willing to do that unglamorous merging, which is why our list is four and not forty. None of this is new, either; in 2006 we did the same thing with svn:externals and a vendored source drop, except the divergence was invisible and the upgrades were archaeology, and the modern fork is the same decision with the divergence rendered as a branch you can diff, rebase, and eventually delete. The delete is the part to aim for: the best fork is the one that goes away, either because upstream took the change or because you stopped needing it, and until that day the fork is not a statement about anybody, it is just a branch with an upstream and a reason.
