My first open source PR took six months to merge
A junior dev in Porto spots a bug in a library half the internet uses, opens a PR, panics, and walks away for half a year. What happens after is the actual story.
Inês found the bug on a Saturday in October, in a café on Rua de Cedofeita in Porto that sold filter coffee in mismatched ceramic cups. She was twenty-three, four months into her first job — a small fintech that paid her in euros and trust — and she was writing a script to backfill some user records. The library she was using was a date utility that, if you've shipped anything in JavaScript in the last decade, you've almost certainly imported. Not the famous one. The other one. The smaller one that the famous one's maintainers grudgingly recommend now.
Her script kept producing a date one day off, but only for a specific window in March. She thought it was her code. She rewrote her code. She thought it was her timezone. She changed her timezone. She thought it was her laptop. She restarted her laptop. The café owner, an older man named Rui who had decided Inês was now a regular, brought her a small glass of water without being asked. She drank it. She tried again. The bug was still there.
Around 4pm she got the library's source open in another tab and started reading. It was, like a lot of beloved small libraries, weirdly readable — the kind of code where the author had clearly cared about whether you could understand it on a Saturday afternoon. She found the function. She read it twice. The third time, she saw it: a comparison that used < where it should have used <=, in the branch that handled the boundary day of daylight savings. One character. The entire library, for a thirty-six-hour window once a year, returned the wrong day for a small subset of inputs.
She felt the specific flavour of nausea you feel when you spot something that nobody else has spotted. Then she felt the second wave, which is the certainty that you are wrong and have misunderstood, because there is no way that you, four months in, in a café in Porto, have found something the rest of the internet missed. She wrote a failing test. The test failed. She fixed the character. The test passed. She wrote another failing test for a different DST boundary. It passed too. The bug was real. The fix was four characters of diff.
She spent the rest of the weekend not pushing the PR.
The activation energy is emotional, not technical
Here is the thing nobody tells you about your first open source contribution: the code is not the hard part. The code, almost by definition, is small. First PRs are typo fixes, failing-test repros, a missing edge case, a one-line guard against a null. They are the kind of change that, inside your day job, you would push to main without ceremony, between sips of coffee, while half-listening to a standup.
The hard part is that you are about to send a piece of writing — because that's what code is, in this context — to a stranger on the internet who has earned the right to tell you you're wrong. And the stranger is busy. And the stranger has read ten thousand of these before yours. And the stranger has no obligation to be kind to you, no HR department to escalate to, no shared standup at 9:30am where things get patched up. You are walking into a room where you don't know the rules, in a building you didn't pay rent on, and asking the landlord to change a lightbulb.
Inês knew this without being told. She had read enough GitHub threads to know that maintainers came in two flavours, which I'll get to in a minute, and that the cost of guessing wrong about which flavour you were dealing with was a public, indexed, forever-googleable record of having been told off.
She pushed the PR on Sunday night at 11:47pm, with the kind of branch name (fix-dst-boundary) and commit message (Use <= for DST boundary comparison; add tests) that she hoped read as professional. She included a one-paragraph description, a link to a repro, and the two new tests. She closed her laptop. She did not sleep well.
Two terse comments, three weeks of silence
The maintainer replied on Tuesday. The reply, in full, was two comments on the diff. The first said: "why <= here? need a comment explaining the boundary case." The second said: "tests should live in dst.spec.ts not utils.spec.ts." That was it. No greeting. No "thanks for the PR." No emoji, no exclamation marks, none of the warmth that Inês had subconsciously hoped for and would have been embarrassed to admit she was hoping for.
She read the comments seven times. Each time, they got colder. By the seventh reading, she had constructed, in her head, an entire personality for the maintainer: a man in his forties, probably American, probably annoyed, probably regretting having ever published the library, definitely judging her. She drafted three replies. She deleted all three. She told herself she would respond at the weekend, when she had time to do it properly.
The weekend came. She didn't respond. The next weekend came. She didn't respond. The third weekend, she actively avoided the GitHub tab when she opened her browser. By week four, the PR had drifted out of the top page of her notifications, and she had achieved the specific kind of peace that comes from having a small, unfinished, slightly shameful thing parked in the back of your mind, where it will sit, undisturbed, indefinitely.
What good maintainers actually want
Here's what Inês didn't know yet, and what took her another six months to learn: those two terse comments were not a rejection. They were, in fact, the maintainer doing exactly what a good senior engineer on her own team would have done. He had read the diff. He had identified two specific things he wanted changed. He had asked for them without ceremony, because adding ceremony to every code review is how maintainers burn out by 34.
If she had been sitting next to him at a desk, his comments would have read as helpful, even friendly. The exact words "why <= here? need a comment explaining the boundary case" are, in person, two seconds of conversation and a quick nod. On GitHub, in text, with no body language and no shared context, they read as cold. They are not cold. They are compressed.
Juniors think maintainers want perfection — a flawless diff, an eloquent description, a tone of appropriate humility. They think the maintainer is grading them. They are not. Maintainers, the good ones, want three specific things, and once you understand what they are, the entire experience of opening a PR rewires itself.
The three things a good maintainer wants
- 1.A clear repro. Not an explanation of what you think is happening — a minimal piece of code or steps that makes the bug appear on their machine. If they can paste your repro into a test file and watch it fail, you have done 80% of the work of getting your PR merged before they have read a single line of your fix.
- 2.A small diff. The smaller the diff, the lower the cognitive load of reviewing it, and the higher the chance it gets merged this week instead of next quarter. A four-character fix with two tests is a maintainer's dream. A 400-line refactor that also fixes the bug is a maintainer's tax bill.
- 3.A willingness to revise. Not eloquence. Not eagerness to please. Just a calm, prompt acknowledgement that you read their feedback and will go change the thing. The maintainer is not testing you. They are routing work. If you can be routed, you will be merged.
Inês, sitting on her unanswered PR, had done one and two perfectly. The thing she had failed at was three — not because she was unwilling, but because she had read the two terse comments as a verdict instead of a request, and a verdict is not something you respond to with a new commit. A verdict is something you flinch away from. So she flinched.
Six months later, a Slack thread
It was April. Inês had moved to a slightly better job, still in Porto, in an office on the second floor of a building that had once been a haberdashery. The radiator clanked. She had started, in a small way, to think of herself as a real developer. She had shipped things. She had written a small internal tool that the rest of the team actually used. Her impostor syndrome had not gone away, but it had taken a small step back, the way a cat does when it decides the room is, on balance, safe.
Then one Wednesday morning, in a Slack channel of about eighty developers from across the city, someone posted a screenshot of the exact bug. Same library. Same DST boundary. Same one-day-off result. The poster — a developer she didn't know, working at a logistics company — asked if anyone had run into it. Three people replied yes. One of them said: "there's an open PR for this from like six months ago, looks like it never got merged."
Inês felt every blood vessel in her face open at the same time. She opened GitHub. She found the PR. There it was, still open, still her, still those two terse comments at the top, still untouched since October. She read the comments again. They had not, somehow, gotten any colder. In fact, with six months between her and them, they read differently. They read like notes from a colleague. They read like a quick code review from someone who trusted her to handle it.
She closed Slack. She wrote a reply: "Sorry for the delay on this — pushing the changes now." She added a comment explaining the boundary case, in the exact spot he had asked for. She moved the tests into dst.spec.ts. She rebased onto main, which had moved forward a non-trivial amount. She ran the test suite. She pushed. The whole thing took ninety minutes.
The maintainer merged it four hours later. His comment, in full, was: "Thanks, nice find."
The two kinds of maintainers
It's worth saying this plainly, because nobody told Inês and it would have saved her three weeks of dread: there are roughly two kinds of open source maintainers, and you can usually tell which one you're dealing with before you spend a weekend on a PR. Knowing the difference is most of the skill of contributing to open source as a beginner.
The first kind writes back to you like a senior engineer on your team. Terse, specific, no warmth in the prose but no malice either. They will request changes without softening them. They will close issues without ceremony. They will, when you finally land your PR, say "thanks, nice find," and you will, for reasons you can't explain, feel like you got an A. This is the kind of maintainer Inês had, and it is the kind most beginner-friendly libraries are run by.
The second kind writes back like a tired god. Long, didactic comments. Public sighs. Replies that begin with "as has been discussed many times in this repository." PRs left open for years with no comment, then closed without explanation. Issue templates that read like legal disclaimers. You can spot this kind in about two minutes of scrolling the issues tab. Don't spend a weekend on their library. Not yet. Not for your first PR.
Here is what to actually look at, in order, before you commit a weekend.
- The recently closed PRs. Are they merged, or closed-without-merge? What does the maintainer's last comment look like on each one? Is there a contributor who isn't the maintainer? Are first-time contributors getting in?
- The CONTRIBUTING.md file. Is there one? Is it reasonable, or is it a 4000-word wall of conditions? A short, friendly CONTRIBUTING.md is the strongest possible signal that the maintainer wants you there.
- The issue labels. Are there
good first issueorhelp wantedlabels actually being used, or were they added in 2019 and forgotten? Look at the dates. - The tone of replies to bad PRs. Find a PR that was wrong, or sloppy, or duplicated. Read how the maintainer told the contributor no. That tone is the tone you will get when you slip up. Decide if you can handle it.
If a repo passes those four checks, your weekend is safe. If it doesn't, find a different repo. There are millions of them. You don't owe any particular one your first contribution.
A taxonomy of good first contributions
Inês's first PR was a four-character bug fix with two tests, which is more or less the platonic ideal of a first contribution. But it's not the only shape. Most juniors get stuck on the idea that their first PR has to be a Feature, or a Refactor, or some other capital-letter thing that justifies the time. It doesn't. The warm-up is the point. Here is the actual ladder, from easiest to hardest, of contributions that are unambiguously useful and unambiguously good practice.
- 1.A documentation typo. Not a waste of time. Not below you. A typo fix is a complete end-to-end run of the PR workflow — fork, branch, commit, PR, review, merge — with the lowest possible code-review surface. You learn the muscle memory. The repo gets better. Nobody loses.
- 2.A failing-test repro for an existing open issue. Find an issue labelled
bugwith no PR attached. Write a test that fails because of the bug, and submit just the test, marked as.skipor in abugs/folder. Maintainers love this. It turns a vague report into a tractable problem, and the next contributor — possibly future you — has a clear starting point. - 3.A missing edge case. Find a function with tests, identify an input it doesn't handle (null, empty string, leap year, negative number, unicode), write a failing test, write the fix. This is what Inês did, and it's the sweet spot: small, defensible, and usually obvious in hindsight.
- 4.A single utility refactor. Take one internal helper function that has grown a bit gnarly, and clean it up without changing its behaviour. Add tests if there aren't any. Keep the diff tight. This is harder, because you're now arguing about taste, but it's still bounded.
- 5.A small new feature, after you've already had two PRs merged in the same repo. Not before. Two prior merged PRs gives you context, trust, and the right to propose. Walking in cold and proposing a new feature on your first contribution is the single most common way good first contributions die in review.
Notice that none of those involve writing more than about a hundred lines of code. That's deliberate. The smaller the diff, the higher the merge rate, the faster the feedback loop, the more you actually learn. Big PRs are for when you have a relationship with the repo.
Things to do before you open any PR
Inês, six months and one merged PR later, started keeping a personal checklist. She never wrote it down formally — it lived in her head, in the slightly defensive way you carry a list of things you wish someone had told you. It looked roughly like this, and it is, I think, the closest thing to universal advice that exists for first contributions.
- Read CONTRIBUTING.md, all of it. Even the boring parts. Especially the boring parts. The boring parts contain the things the maintainer is tired of repeating in review, and skipping them is how you become one of those repetitions.
- Search closed PRs for similar attempts. If someone has tried this fix before and been told no, you want to know why before you spend three hours on it. Search for the function name, the file name, the error message. Read the closed PRs first.
- Write the failing test before the fix. Two reasons: it forces you to specify the bug precisely, and it gives the maintainer something to verify without trusting your description. The failing test is the receipt.
- Name the branch boringly.
fix-dst-boundary, notcrush-this-bug-finally. The maintainer is going to see the branch name in their notifications. Boring is respectful. - Write the commit message the way the maintainer writes their commit messages. Open the repo's git log. Read the last twenty commit messages. Match the style — imperative mood, capitalisation, length, whether they reference issues, whether they wrap at 72 characters. This takes ninety seconds and signals more than any cover letter you could write.
- Keep the PR description short. Three sentences: what the bug is, what the fix is, how to verify. Link the issue. Don't apologise. Don't pre-emptively explain things the maintainer hasn't asked about. They will ask if they want to know.
What happened after
The strange thing about the merged PR was not the merge itself, which took, as I said, about four hours. The strange thing was what happened in the following weeks. The maintainer — whose name was Tomás, who turned out not to be a forty-year-old American but a thirty-one-year-old Brazilian living in Lisbon, two and a half hours down the coast from Inês — opened an issue a week later, tagging her in it, asking if she had thoughts on how to handle a related case.
She did have thoughts. She replied. He replied. They went back and forth a few times. The exchange was short and businesslike and, in the way that text conversations with people you've never met sometimes are, slightly addictive. Two weeks later she opened a second PR on the same library. He reviewed it in twenty minutes. Two terse comments. She revised. He merged.
Eighteen months later, the local JavaScript meetup in Porto — which by then Inês was helping organise — needed a speaker for a slot on edge cases in date handling. She emailed Tomás. He took the train up. They co-presented, in a co-working space above a bakery, to about forty people, most of whom had at some point shipped a date bug to production. Afterwards they got dinner at a place near the Mercado do Bolhão, and she finally told him about the three weeks of silence on her first PR, and the six months it took her to come back. He thought it was funny. He also thought it was sad. He said he wished he had known — he would have written something warmer. He said he had stopped writing warmer things in 2021 because he was burning out, and he was, in retrospect, not sure if that had been the right tradeoff.
Now they email each other roughly once a month. Sometimes about code, mostly not. He has come to her wedding. She has not, yet, come to his — he is not married — but she has met his partner twice. The thing she had treated, in October, as a verdict from a stranger on the internet had turned out to be the start of a friendship with someone who, in any other configuration of the universe, she would never have met.
What she does now
Inês is twenty-six now, a senior at a different company, and she mentors two juniors. The first thing she does, when one of them asks about contributing to open source, is steer them away from the famous libraries and toward the small, well-run, beginner-friendly ones. She used to keep a list in a private Notion page. Now she sends them to the curated list of beginner-friendly repos in the onedb directory, because it's better-maintained than her Notion page was, and because she trusts the people picking the repos to apply the same four checks she would: recently merged first-time PRs, a friendly CONTRIBUTING.md, real good first issue labels, and a maintainer whose tone, when they say no, is one she would want a junior to read.
She tells the juniors, every time, the same three things. The PR is going to feel bigger than it is. The maintainer's reply is going to feel colder than it is. And the cure, when you inevitably stall out at week three, is not to write a longer apology — it's just to push the next commit. Small, ugly, finished. That's the whole shape of it.
Rui still runs the café on Rua de Cedofeita. He still brings her a small glass of water without being asked. She doesn't write code there as often anymore — these days it's mostly Slack messages and meeting notes — but every October, on the anniversary of the bug, she opens her laptop in his café and finds a tiny open source thing to fix. A typo. A failing test. A missing edge case. She does it in one sitting. She pushes before she leaves. She has never, since that first one, let a PR sit for six months again.
keep reading →
- 01
One purple pen: notes from an editor who refused to rewrite
A story about a magazine editor, a stubborn writer, and the small lavender mark that ended up shaping how we ship websites at onedb.
- 02
The night the SaaS site rewrote itself
A founder, a deadline, a sleeping co-founder, and the eleven-hour rebuild of a SaaS landing page that taught me everything about restraint.