Essays / Self-hosting
Upstream ships 120 commits a week. I forked their app anyway.
Everyone knows the rule about forking. Don’t. Upstream will outrun you. The merges will turn ugly. Six months in, you’re curating a museum.
I forked anyway.
Some background. My businesses are run day to day by a team of AI agents, and that team lives in Buzz, Block’s open-source Slack alternative. We self-host it. Our chat, our server, our data. The iOS app does the chat part well. But it had two limits I couldn’t live with, and neither was a settings problem.
First, the app had no push notifications. None. Not broken, not switched off. Absent. I verified it at the source: the App Store build carries no push registration, no push entitlement, no background mode for it. The server half shipped upstream in July. The phone half is still a draft. And even when it ships, it won’t help anyone who self-hosts, because Apple only delivers a notification signed by the team that owns the app. That team is Block. My server can never wake their app. No setting, no plugin, no polite issue on their tracker changes that.
Second, deep links went nowhere. A link to a message names a channel and a message id, but not the community they belong to. The app resolves it against whichever community happens to be open. I’m in four. Tap a link from the wrong one and you get “channel not found”. A notification that opens onto a dead end is worse than none at all. It trains you to stop tapping.
That pair is the real test for anyone weighing a fork. Config can rename and recolour. It cannot add code that was never written, and it cannot re-sign someone else’s app. When your blockers live there, you either wait or you own the app.
Waiting was not a plan. The push question has sat unanswered on their tracker for weeks. Nobody outside Block has built a workaround either. We checked the forks, the code search, the app store. Nothing to adopt. So we forked.
The licence made it easy. Apache 2.0 says fork away, with no obligation to publish. The trademark clause says the name stays behind, so our fork of the iOS app carries its own name and its own icon. It sits on my phone beside the original until it earns its keep.
Then the scary number. Upstream ships about 120 commits a week. We counted before branching: 121 in the previous seven days. That kind of upstream is a river. Step in carelessly and it carries your changes away every week.
So the fork lives by one rule. Additive only.
Changes go in new files and new code paths. We do not edit upstream’s files, because every upstream file we touch becomes a future merge conflict, and at this pace the future arrives weekly. The whole diff stays small enough to hold in your head: the app’s identity, plus a deep-link fix. Links in our world now carry the community they belong to, and the app switches to it before opening the message. A link is untrusted input, so the switch only goes to a community already on the phone. It can never join anything or connect anywhere new, and it gets one attempt. When a link fails, the app names the community that owns the message instead of shrugging.
If a change can’t be made additive, we don’t carry it. We send it upstream as a pull request and wait like everyone else. The fork exists for exactly the things upstream cannot or will not do for a self-hoster. Everything else stays their problem, which is where you want most problems to live.
Push proper is still coming. Owning the app is what makes it possible at all, since a notification can only be signed by the team that owns the app, and that team is now mine. Meanwhile a stopgap watches our own server and hands each alert to another app that already knows how to reach my phone. Ugly. Works. It retires the day native push lands.
One quieter lesson from the deep-link work. Our first fix matched communities by host and port. That looked careful and was wrong. The runtime reports different default ports for different URL schemes, so a community stored one way never matched a link written the other way, and the switch silently never fired. The right answer was less checking. The hostname is the identity. Fork work is mostly this. Small, humbling corrections to assumptions the original authors never had to test.
Was it worth it? Tapping an alert now opens the right message in the right community, every time, from any of the four. Before the fork, that sentence was fiction.
Upstream will ship another hundred-odd commits this week. That used to sound like a threat. Against a two-change diff it’s just weather. A fork travels light or it dies. Ours packs two changes and keeps walking.