You start a project with the latest tools. You’re shipping features left and right, tests are fast, CI is green, life is good. Everything is perfect!
Then one day, a new version of your favorite framework drops.
“Huh, some cool features there. Anyway, back to work.”
A few months later, a major release drops.
“Oof, there are breaking changes. These early releases often have bugs. Let’s wait until more people try it and it’s stable.”
Time passes. You try to update some other library. Requires framework >= 3.2.
“Our version isn’t supported? Wait, it is deprecated already?! Fine, a monkey patch will do.”
This cycle repeats: write a monkey patch here, a compatibility shim there. A million lines of code and a few hacks later, that slick, modern codebase has decayed into abandonware, held together by bubblegum and shoelaces.
Entropy swallows you
There is no hope in waiting. Even if you don’t change, the environment around you sure will. Nature is relentless, and entropy always wins. Vendor dependencies, lock versions, never update anything — time will still erode your software.
Package registries remove old builds, CI images disappear, hosting platforms stop supporting your runtime. Keeping that alive with patches and workarounds will be more and more work, until one day, it simply breaks. The world moved on, and you’re left behind.
Stay ahead of the clock
Treat it like brushing your teeth, exercising, or doing house chores: routine, boring, and fundamental. The earlier you do this, the cheaper and easier it will be.
If you wait too long, the app will become so painful to work with that none of your devs will want to touch it, hiring will be a nightmare, security vulnerabilities will pile up, upgrades will seem so hard that a rewrite in microservices suddenly will look like a rational solution. And if you go that route… well, you just got yourself a whole new set of problems.
Luckily, there are tools to help us stay ahead in this game:
- Dependabot: let it handle what it can.
- Minor and patch updates are basically just a “Merge” button away now.
- CVEs are automatically flagged and PRs created.
- Audit your dependencies: your package manager probably has a command for
that (
npm audit,bundle audit).- Integrate it into your CI pipeline to catch issues early.
You don’t need to be on the bleeding edge (all the recent NPM attacks are a testament to that), so a cooldown is healthy.
End of life is here
That’s why I built End of Life, a command-line tool that scans your GitHub repositories and flags any that depend on end-of-life software versions.
Here’s how it looks in practice:
$ end_of_life scan ruby
[✔] Searching repositories that might use Ruby...
[✔] Scanning 27 repositories for EOL Ruby...
Found 2 repositories using EOL Ruby (<= 3.1.7):
┌───┬──────────────────────────────────────────────┬──────────────┐
│ │ Repository │ Ruby version │
├───┼──────────────────────────────────────────────┼──────────────┤
│ 1 │ https://github.com/MatheusRich/my_rails_app │ 2.5.8 │
│ 2 │ https://github.com/MatheusRich/some_repo │ 2.5.0 │
└───┴──────────────────────────────────────────────┴──────────────┘
If you’re clever, you can add it to your CI pipeline and fail the build when using a version that will be deprecated in the near future, so you can plan ahead.
# fail the build if using a version that will be EOL in the ~6 months
$ end_of_life check ruby@$(ruby -v | awk '{print $2}') --max-eol-days-away=180
┌─────────────────┬──────────┬──────────────────────────┐
│ Product Release │ Status │ EOL Date │
├─────────────────┼──────────┼──────────────────────────┤
│ ruby@3.2.9 │ Near EOL │ 2026-03-31 (in 3 months) │
└─────────────────┴──────────┴──────────────────────────┘
Back to the future
If you got yourself into an old version, there’s still a way out. We need to tourniquet that codebase before moving forward. Here are some strategies to help you:
- Use Rails LTS to get security patches for old Rails versions. This will buy you some time to plan your upgrade, but it’s not a long-term solution for a living codebase.
- Use Ruby Next to backport modern Ruby features to older versions. This might be necessary to run newer dependencies.
- Use a dual boot upgrade strategy to gradually migrate your app to a newer version without a feature freeze, massive rewrite, or downtime.
Learn from the lessons of GitHub. With time and effort, you too can be back on latest!
Need a hand?
Feeling stuck with your legacy code? Can’t make time for updates while juggling new features? Afraid of touching anything because it might break everything? We’ve seen it all and we’re happy to help. Reach out to us and let’s discuss how we can assist you in modernizing your software without the headache.
Prevention is better than cure. ⏱️ Tick, tock…