Walking Into Someone Else's Stalled Project
Coming into a stalled open source project means doing the unglamorous foundation work before the visible work makes sense. Texture from a week of getting flow back.
The repo had been quiet for months when I arrived, and I want to describe what I found gently because the situation is one I have a great deal of sympathy for. Not abandoned, not officially. The maintainer was around. Some issues were getting triaged when he had the time. New community PRs were arriving in a steady trickle. None of them were getting reviewed or merged. The most recent merged PR I could find on the morning I sat down at my desk and opened the project for the first time was a small fix from earlier in the year. The contributor PRs that had landed in the queue since then were sitting open with a few sympathetic comments underneath them and no apparent path to landing.
This is a recognisable shape. Not failure, not abandonment, just the slow drift that happens when the original maintainer is dealing with the rest of their life and the project that started as a side hobby has accumulated more user demand than the side-hobby budget can absorb. There is nothing wrong with this. It is the natural lifecycle of an open source project that is doing well enough to attract contributions but isn’t anyone’s full-time work.
What I want to write about is what it actually felt like to walk into that, with the goal of getting things flowing again, and what the work turned out to be in practice. Almost none of it was the satisfying feature work that makes screenshots. Most of it was the kind of foundation work that nobody talks about, because it’s the work that makes everyone else’s work possible.
The first week was almost entirely scaffolding
Before any of the user-facing features in the queue could land safely, I had to do work the queue itself didn’t represent. The first commit I made on the project was a CLAUDE.md documenting the codebase architecture for AI-assisted contributors, plus an updated GettingStarted.md covering FVM (the Flutter version manager the project actually used, which wasn’t documented anywhere). I wrote it for me first. Reading my way into someone else’s codebase is faster when I’m forced to write down what I’ve learned, and the artefact left behind is usable by every contributor that follows.
Then came the regression-test pack. The test suite when I arrived had 135 tests, mostly covering older parts of the codebase. The features that had landed in the previous few months were almost entirely untested. Before I started landing more features, I added 119 new tests covering the existing-but-untested code paths. That sounds defensive and it is. The reason is that I was about to start merging community PRs at speed, and I wanted a regression net underneath the existing behaviour before I started adding things that might collide with it. New behaviour can be tested by its own author. Pre-existing behaviour without coverage is the silent risk in any feature merge, because the merge might break it and nobody would notice.
The third piece was establishing a release flow. The project nominally had a develop branch and a main branch but the boundary between them had blurred. Some PRs targeted develop, some targeted main, some looked like they’d been merged into both. The flow we settled on was that everything goes to develop, then a periodic develop → main PR cuts a release once the develop branch is in good shape. Simple, common, and uncontroversial. The point of writing it down was not the novelty. The point was making it explicit so that contributors who showed up after me could find the answer to “where does my PR go” without having to guess from the recent commit graph.
That set of three pieces, the architecture docs and the regression tests and the release flow, took most of the first day or two of the work, and produced no new features and changed nothing that any user of the app could possibly see. It was, instead, the kind of quiet preparatory work that makes everything after it possible, and I have come to think of it as one of the most important parts of the whole effort even though it is the part that looks the least like progress from the outside.
Reading other people’s PRs in good faith
The largest single thing I shipped early in the week was a port of seven features that had been sitting in a contributor’s fork. Erik (different Erik) had built a bundle of small useful improvements: a quick-weight widget on the home screen, a weekly weight rate goal, a 2024 update to the activity compendium, an extended diary calendar range, direct text input alongside sliders in calculations, a fix for some broken nutrient label concatenation, and daily meal reminder notifications. The bundle sat in his fork at parity with our codebase from a few months earlier. To land it, the work was rebasing all seven features onto our current develop, resolving conflicts that had accumulated during the months our codebase had moved on, fixing things the conflicts produced, and verifying behaviour on each feature individually before merging the lot.
This is the kind of work that’s hard to describe as productive without context. There’s no new code being designed. The features were already designed and implemented by someone else. The work is the patience of going through someone else’s commits one at a time, understanding what each was for, deciding whether anything in it conflicted with the current codebase or other features in the bundle, and producing a single coherent landing. That work is genuinely valuable and it’s also genuinely thankless, in the sense that the visible artefact is just the merged PR, not the seven hours of careful reading underneath it.
What I want to say about it is that reading other people’s PRs in good faith is its own skill. The instinct, when you’re a maintainer with limited time, is to read someone else’s contribution looking for reasons not to land it. That’s how queues stall. The reasons not to land things accumulate, the threshold for merging keeps rising, and after enough months the contributor stops opening PRs because they’ve stopped getting merged. The way out of that pattern is to read PRs looking for what’s right about them, then dealing with the things that need fixing as fixable problems rather than blockers. Most contributor PRs are workable if you put the work in. The work is not nothing. But it’s almost always less than the cost of the queue stalling further.
The CONTRIBUTING file that came late
After about five days of merging, an issue came through where a first-time contributor had opened their PR against main instead of develop, because nothing on the repo told them which one to use. We rebased and merged it, but it surfaced something I should have done earlier: write down the contribution rules for newcomers, in a place GitHub would surface to them.
CONTRIBUTING.md landed two days before this post is dated. It covers the develop-branch policy, the localisation workflow (the project has eight locales and adding a new user-facing string is a sixteen-file change before the build will pass), and pointers to the existing material on environment setup and codegen commands. It’s short on purpose. The things a typical contributor will care about (where does my PR go, what do I need to update for a new string, how do I run the tests) are the first things you see. The things a contributor only cares about if they’re touching deeper internals (DI registration order, Hive type IDs, BLoC layout) stay in CLAUDE.md.
The honest reflection here is that I should have written CONTRIBUTING.md on day one. Not because there was a queue of contributors waiting for it, but because the act of writing it would have surfaced the conventions I was implicitly assuming and forced me to check whether they were actually correct. Writing things down for newcomers is one of the cleanest ways to find out which of your own assumptions don’t survive contact with someone who doesn’t already know.
The CI changes that mattered most
The most visible CI change was wiring up PR validation properly. When I arrived, the workflow was running on push to develop and main but not on pull_request, which meant a PR’s validation was, in effect, “did the author run the tests locally before opening.” Real PR validation across linux checks, Android build, iOS build, and an iOS integration test landed across a few PRs over the week.
The CI change that mattered most was smaller and stranger. Once PR validation was firing on pull_request, every push to develop while a develop → main release PR was open started firing both the pull_request synchronize event and the push event for the same SHA, doubling every job in the checks list. The fix was three lines in the trigger matrix: drop develop from the push triggers, since pre-merge validation was now covered by pull_request for every flow. Direct pushes to develop are forbidden by project policy anyway, so the lost trigger was moot.
What that change actually did, beyond stopping the duplicate runs themselves, was quietly change the feel of the repo to work inside. CI runs that finish in half the time make every other piece of work feel a little less heavy than it otherwise would, in ways that compound across a working day. The reviewer is not waiting as long for green ticks before they can finish their review, the contributor is seeing feedback on their PR faster than they expected, and the maintainer is no longer having to mentally filter out the duplicate red ticks every time they scan a checks page looking for something real. Three lines of YAML changed all of that, which is the kind of small ratio I keep encountering in CI work and the kind that makes me want to take small CI changes more seriously than they tend to get taken.
What I learned about taking on someone else’s project
The thing I keep returning to is how much of the work was building scaffolding for the work, rather than the work itself. The architecture documentation, the regression tests, the release flow, the CONTRIBUTING file, the CI fixes. None of that is feature work. All of it is what makes feature work go fast without breaking things. A project that’s been quiet for months has accumulated a lot of accumulated mismatches in its scaffolding, and the temptation when you arrive is to jump straight to merging the visible queue. That instinct produces short-term throughput at the cost of medium-term breakage. The slower start, where you fix the scaffolding first, pays for itself within the first week.
The other thing I want to be honest about is the emotional texture. There is a particular feeling to opening a project you didn’t write, reading someone else’s code carefully, and trying to leave it better than you found it. It’s not unlike being a guest in someone’s house. You want to be useful but you also don’t want to rearrange their kitchen without asking. Simon, who built OpenNutriTracker and has carried it through every version that came before mine, is still the maintainer. The project is his. The shape of it is his. The work I was doing was at his invitation, and the question of how much to change versus how much to leave alone is a question I came back to more than once.
What I tried to do, and what I think I mostly succeeded at, was to leave the existing patterns in place wherever I could and to add new patterns only where they were genuinely needed by the work in front of me. The DI structure stayed the same as it was, the bloc layout stayed the same, and the overall shape of the codebase stayed the same as it had been when I arrived. New features composed the existing primitives rather than introducing new abstractions on top of them, and the tests followed the patterns the existing tests already followed, so that the aesthetic of the project, such as it was, did not end up disturbed by the additions.
That, I think, is the part that actually matters when you walk into somebody else’s house and want to be helpful inside it. You can fix the loose floorboards and patch the roof where it has been leaking, and you can replant the garden if it has been neglected; what you do not get to do, even with the best of intentions, is rearrange the furniture. The original character of the place is the thing the people who lived there in the first place came for, and the people who arrive later came for it too, and that character is something to look after rather than to improve upon without permission.
Where it is now
The queue of waiting PRs is empty now, with the contributor PRs that had been sitting open all merged into develop, the release flow working as designed, CI runs finishing quickly, and the regression test pack sitting underneath it all as a safety net. The next time somebody opens a new PR against the project, the path for it to land is short and well-marked. When Simon comes back to the project, whenever that is and whatever shape his return takes, what he comes back to should look like a project that has been quietly looked after in his absence rather than one that has been remodelled while he was away.
That, I think, is the right shape for the outcome to have, even though the work to get there was almost entirely unglamorous. Most of it was reading and writing and waiting for builds to finish, in unpredictable rotations across the working week. The thing that strikes me about it now, when I look back at the whole stretch, is how much of the week was the same kind of work happening over and over again: read the code, write down what you found, run the tests, merge the PR, and then go back to the start of the loop. There was no single decision in any of it worth telling as a war story; there was a week of small careful decisions, compounded into a project that is now working again.
That, I have come to think, is what most maintainer work actually is, when you strip away the parts of it that get written about. It is not heroics, and it is not big architectural reorganisations, and it is not the kind of thing that makes for a conference talk afterwards. It is just the patient accumulation of small decisions, made carefully and one after another, until the project starts moving on its own momentum again. The work I did this week on somebody else’s stalled project was not really different in kind from the work I do on shared infrastructure at my day job; it was the same underlying instinct, applied to a different room. Notice what is broken, fix the smallest thing first, keep the existing tenants happy in their existing patterns, and leave a recognisable path for whoever comes next.
That is enough to be doing with a week, and I want to say it out loud rather than apologise for the modesty of it. It is also more, I think, than most of us give ourselves credit for when the work does not make screenshots and nobody is paying particular attention to it. The project is running again, and the people who depend on it can keep depending on it, and the maintainer’s house is in better shape than I found it.
I am still thinking about what it means to walk into someone else’s slow patient work and try to add to it without disturbing it, and I do not have a clean answer for that, beyond the sense that being careful matters more than being clever. If you are reading this and you have ever been the maintainer of something you loved that drifted while you were dealing with the rest of your life, I want you to know that it does not have to stay that way, and that someone arriving to help you is almost always a good thing rather than a verdict on what came before. That is the kind of outcome I want my work to keep producing, in this project and in whatever others I am lucky enough to be a guest in next, and I am quietly glad I got to spend this week the way I did.