Five Years Without a Single Code Change — Then It Broke

ConnectStats has been alive for over 15 years now. If you’re not familiar with it, it’s an iOS app I built for runners, cyclists, and triathletes who want to actually understand their training data from Garmin and Strava. Not just see it, but dig into it — compare seasons, plot trends over years, find patterns in heart rate zones and pace distributions that the official apps don’t surface.

I’m proud of the app. Users have been generous with their feedback over the years, and some have become friends. But I’ll be honest: between work and life and picking up flying as a hobby, ConnectStats had quietly slipped into maintenance mode. And then maintenance mode slipped into no-maintenance mode. For roughly five years, I barely touched the code. I’d occasionally check that things were still working, but no releases, no bug fixes, no features.

And somehow — five years of nothing — it just kept working. Users kept downloading it, kept using it, kept sending me the occasional kind email. A fifteen-year-old app, running on a codebase I hadn’t updated in half a decade, quietly doing its job. I’ll admit: I was both pleased and slightly amazed.

Then, inevitably, it broke.

The emails started coming

Users were frustrated. ConnectStats kept asking them to log in to Garmin, and after they’d successfully authenticate, the app would just… ask them to log in again. An endless loop. The app was essentially unusable.

Around the same time, I received an email from Garmin announcing they were migrating from OAuth 1 to OAuth 2. That seemed like the obvious culprit. But understanding what needed to change meant understanding how the entire authentication flow worked — the dance between the iOS app, my server, and Garmin’s API.

I couldn’t remember any of it.

This wasn’t a simple codebase. ConnectStats has the iOS app with all its data visualisation, but behind it sits a PHP server that handles the Garmin OAuth handshake, proxies API requests, manages user sessions, and processes activity data. I’d built this infrastructure years ago and hadn’t thought about it since. The authentication flow alone involves multiple redirects, token exchanges, and callback handlers split across the app and the server. Untangling it from memory was not going to happen.

The detective work begins

I turned to Claude Code — an AI coding assistant I’ve been using extensively for my other projects. Over the past year I’ve developed a workflow where I use a custom “skill” that instructs the AI to analyse a codebase and produce structured design documents — breaking down the architecture, mapping data flows, documenting how components interact.

I pointed this skill at the ConnectStats codebase — both the iOS app and the server — and let it work. What came back was a structured analysis of code I hadn’t looked at in years. The authentication flow, the API integration layer, how the server proxied requests to Garmin, how tokens were stored and refreshed. It was like having a new team member read through everything and write the documentation that should have existed but never did.

With that map in hand, Claude guided me through the debugging. It asked me to check specific logs on the server, and when I shared what the logs showed, it traced the flow further. We followed the authentication chain step by step: app sends credentials, server receives them, server talks to Garmin, Garmin responds, server stores the token, app tries to use it…

And that’s where the trail went somewhere I didn’t expect.

The real problem wasn’t the code

The authentication was actually completing successfully. The OAuth flow was fine — Garmin was happy, the server was receiving valid tokens. But something downstream was failing silently. Claude, reading through the server code and the logs, identified a data cleanup job — a background process that was supposed to periodically clean up old temporary files and session data to keep the disk tidy.

The cleanup job had been failing. For a long time, apparently.

Now, this job was configured to email me when it failed. And my server had a separate monitoring script that would email me if disk usage got too high. Two safety nets. Both should have warned me long before anything broke.

Neither email ever reached me.

It turned out that Google had, at some point during those five quiet years, tightened their requirements for accepting email from external mail servers. The specific Postfix configuration I was running on my server no longer met Google’s standards. So the failure notifications were being sent into the void. The disk monitoring alerts — same thing. Silent rejection.

Without the cleanup job running and without any alerts getting through, the server’s disk had been slowly filling up. Five years of accumulated data, temporary files, logs — all piling up with no one watching. Eventually the disk filled completely, and that’s when the authentication flow broke. The server couldn’t write the session tokens to disk, so every login succeeded at Garmin’s end but failed silently on mine. From the user’s perspective: log in, get asked to log in again, forever.

So to recap the chain: Postfix configuration becomes outdated → Google silently rejects server emails → cleanup failure alerts never arrive → disk monitoring alerts never arrive → cleanup job fails unnoticed → disk fills up over years → server can’t write auth tokens → users stuck in login loop. Five dominos, and I couldn’t see any of them falling.

The second mystery

With the disk cleaned up, email fixed, and authentication working again, most users were back in business. But a smaller group reported a different, more subtle problem: some of their activities weren’t downloading. The tricky part was that mainstream activities — running, cycling — synced perfectly fine. So most users never noticed anything wrong. It was only people who tracked less common activity types who saw gaps in their data.

This one turned out to be a classic API drift issue. I downloaded Garmin’s latest API documentation, gave it to Claude alongside the codebase, and asked it to cross-reference. It found the problem quickly: Garmin had quietly renamed some of their activity type enums — but only the less common ones. Something like skiing_ws had become just skiing. The core types like running and cycling were untouched. My code was looking for the old names, not finding them, and silently skipping those activities. A bug that only affected a minority of users doing minority activities — the kind of thing that can hide for months.

The fix was straightforward — update the enum mappings in a few places to match the new naming — but Claude’s value was in identifying exactly which places needed changing. These enum names appeared in the API parsing layer, in the activity type classification logic, and in the display code. Miss one, and you get inconsistent behaviour. Claude found them all by reading the full codebase against the new API docs, something that would have taken me hours of careful grepping and cross-referencing.

What I took away from this

The debugging chain is, I think, the more interesting story. It’s not “AI magically fixed a bug.” It’s closer to how a good consultant would work: arrive with fresh eyes, read everything, ask for logs, follow the evidence, refuse to jump to the obvious conclusion (the OAuth 1 to 2 migration was a red herring — it hadn’t actually happened yet), and trace the real root cause through a chain of cascading failures that no single person would spot without methodically working through it.

The skiing enum fix is simpler but equally instructive. The actual code change was trivial. The expensive part was finding where to look — cross-referencing a changed API against a large codebase and making sure every occurrence was caught. That’s exactly the kind of tedious, error-prone archaeology that AI is genuinely good at, because it can hold the entire codebase and the API docs in context simultaneously.

There’s something poetic about a fifteen-year-old app that ran untouched for five years, then got rescued by a technology that didn’t exist when I first wrote it.

But the practical takeaway is more specific: the design documentation workflow I’d built for new projects — where AI analyses code and produces structured architectural docs — turns out to be equally powerful, maybe more powerful, for legacy code recovery. New projects have the advantage of fresh context in your head. Legacy projects are where knowledge loss hurts most, and where the ability to rapidly reconstruct understanding of a forgotten codebase is most valuable.

If you maintain old software — and most developers do, whether we admit it or not — the idea of pointing an AI at your codebase and saying “explain this to me, then tell me what’s wrong” is genuinely transformative. Not because the AI is smarter than you. Because it can read everything at once without the handicap of having forgotten most of it.

ConnectStats is back up and running, now with documentation it never had before. I can’t promise I won’t neglect it for another five years. But next time something breaks, at least the archaeology will be faster.

And to all the users who sent patient, encouraging emails while things were broken — thank you. You’re why the app is still alive after all these years.

9 thoughts on “Five Years Without a Single Code Change — Then It Broke

  1. Brice, many thanks to you (and Claude) for sorting things out and keeping the app alive. I’ve used it for several years now and it’s a great tool for evaluating my cycling from year to year. All the best to you!

  2. That’s brilliant, what a great email. Many, many thanks for sharing it all honestly with us – that’s the level of customer service so many of our leading companies could do with adopting. I can’t emphasis how much I respect you – the app is great, so much better than the Garmin one, and to have it back and working is brilliant.

  3. Dear Brice – thank you so much! Not only for fixing the App together with Claude and include skiing again (and no, it’s not a minor activity in Switzerland ;-)) – but also for your great way of kind communication; sharing your processes in a humorous way – I love all about it!

    Looking forward to skiing stats – thanks a lot! And if you ever make it to this neck of the woods, let me know. I’ll grant for a great skiing day in the Swiss Alps – you’re kindly invited!

    Urs from Lucerne.

    • Dear Brice,
      it’s brilliant to see, what you did over the past couple of weeks with the app. I highly appreciate all your efforts.

      Did you also have the chance to look into the “cycling power-issue” ? I did a couple of test which different profiles & sources as the power data is not synced on a regular basis. Unfortunately, I was not able to find a pattern when the sync will be done and when not.
      Any ideas on this ?

      best,
      Tom W

      • Re: Did you also have the chance to look into the “cycling power-issue”

        I could not reproduce, my power information comes through fine. Can you send me a but report after selecting an activity that does not show power but should? (I fixed that too and receive them again now)

Leave a Reply to Steven KunyCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.