We Published an npm Package. Then the Issues Started.
A blocked TypeScript migration, a month-long PR that went nowhere, and the question that changed everything: why not just build it myself?
I submitted a PR to fix a dependency that was blocking our TypeScript 6 migration.
Then I waited. A week passed. Two weeks. A month. Someone offered $250 in sponsorship to get it merged. That went nowhere too. The part-time maintainer had other things going on. The repo wasn’t abandoned — just slow.
To be clear: the migration wasn’t urgent. Nothing was on fire. TypeScript 6 wasn’t a deadline — it was a direction. We could have ignored the peer dependency mismatch entirely. Plenty of teams would have.
But ignoring it felt wrong. One dependency, one peer compatibility issue, sitting in the way of a clean migration. The kind of thing that sits in the back of your mind. Not critical. Just dirty.
You work around it. You mark it as “known issue.” You wait.
This time I asked a different question.
Why not just build it myself?
The Part Claude Changed
I’d thought about starting an open source TypeScript library before. The things I needed — proper OpenAPI codegen, type-safe React Query hooks, structured API error handling — either didn’t exist the way I wanted, or came with five dependencies I didn’t want to pull in.
The idea wasn’t new. The follow-through was the problem.
Open source maintainership looks straightforward until you’re in it: npm publish setup, CI configuration, semantic versioning, changelogs, release automation, mutation testing — the whole scaffolding that makes a library trustworthy. I knew how to write the code. I was less confident about setting up everything around it correctly. Not impossible. Just enough friction that “just keep it internal” always won.
Claude changed the math.
Not as a code generator. As a senior guide who’d been through the setup before. “Here’s the npm publish configuration. Here’s how Release Please handles versioning. Here’s why your release just jumped to 3.0.0 and how to fix it.” The steps that would have cost me a week of documentation and trial-and-error — those collapsed to an afternoon. I wasn’t reading. I was building, with someone next to me who already knew the path.
We published three packages to the @codewithagents scope: api-errors, openapi-gen, and openapi-react-query. Internal use first. Real tooling I was already using in my own projects. Nothing hypothetical.
Then the issues started.
Real Users. Real Bugs.
Issues #69, #70, #71. In quick succession.
That’s the moment a package stops being yours. Someone you’ve never met is using your code, running into something unexpected, and taking the time to file a report. There’s something disorienting about it — in the best way. You published it publicly, yes. But the first external issue still feels like a surprise.
Issue #75 was different.
Auto-invalidation referencing .detail() on a key factory that doesn’t have a detail property. TypeScript error TS2339 — silent at generation time, visible only when someone hits it in production. A real user, a precise report, a reproducible case.
My response was simple: confirm the severity, write a failing test, fix it.
The difference from how I used to approach bugs: the test came first. Not after the fix. Before. Even though the fix turned out to be a single line of code.
That sequence — test before code — had always been the goal. The gap between goal and practice is what the next section is about.
TDD as the Fast Path
My mind has always understood TDD. Write the test first, then fix the code. The theory is clean. The practice is different.
What actually happens when you debug locally: you reproduce the issue first. Add a logging statement. Poke around until the shape of the problem becomes visible. Fix it in code. Then — only then, when you already know the answer — you write a test for the thing you just fixed. Except by then, the test isn’t driving anything. It’s just documentation of a decision you already made. A checkbox, not a tool. And checkboxes feel like chores.
The honest problem with TDD isn’t discipline. It’s knowledge flow. You often don’t know how to write the test until you’ve already understood the fix. So the test-first sequence breaks down — not because you’re lazy, but because the knowledge doesn’t arrive in the right order.
With Claude, something shifted.
The mechanical cost of writing the test dropped to near zero. While I was still reading the issue and thinking through the failure mode, Claude could write a failing test stub — taking its best guess at what the assertion should look like, running it, watching it fail in the expected way. The test became a probe into the problem rather than a bureaucratic step at the end.
For issue #75: the failing test told us the failure mode before we touched production code. Then the fix was obvious. One line. Test green.
That’s the difference. When writing the test costs almost nothing, you can maintain the sequence even when your knowledge is still incomplete. The test helps you figure out the fix instead of documenting it afterward.
That’s the infrastructure you accumulate when fixing bugs correctly is the default path instead of the aspirational one.
The Honest Number
After the bug sprint, I added Stryker mutation testing.
Stryker modifies your source code — slightly, systematically — and checks whether your tests catch the change. A surviving mutant is a piece of code your tests don’t actually verify. It’s the gap between “tests pass” and “the code does what you think it does.”
Baseline on hooks.ts: 41.77%. 319 surviving mutants.
That number is uncomfortable. I’m not going to pretend otherwise.
But it’s honest — and visible. Before Stryker, those gaps existed too. I just couldn’t see them. Now the baseline is a number I have to live with publicly, and improve deliberately. That’s better than not knowing.
Mutation testing was always the “when I have time” ambition. Now it’s in the CI, it produces a score, and the score is mine to move.
The Insight That Changed How I Think About Dependencies
The month I spent waiting for that PR? That wait is optional now.
Not in the sense that I’ll fork everything I depend on. Most dependencies are actively maintained. Most PRs get reviewed. But the calculation changed. If I’m blocked by a dependency’s backlog, I can build the thing myself, maintain it with AI assistance, and have exactly the test coverage and release process I always wanted. In a weekend. Not months.
The part-time maintainer who didn’t merge a $250 bounty PR wasn’t being unreasonable — maintaining open source is hard, unpaid, and often thankless. I’ve been that person on internal projects. But the dynamic between “wait for someone else” and “build it yourself” has shifted. The option that used to require months of setup now requires an afternoon and a clear scope.
TIP
If a dependency is blocking you and it’s been weeks — ask whether you should be waiting, or building. With an AI co-maintainer, the bar for “small enough to own yourself” is lower than you think.
The real frame: if you ever need to wait days or weeks for a bug fix, that dependency is a candidate to replace. Not because the maintainer is failing. But because you now have the option to own the problem instead of waiting for someone else to solve it.
What Comes Next
The packages are real now. Users are filing issues. The mutation score is a number I publish even when it’s uncomfortable.
That’s what open source actually is — not the publish moment, but everything after. The responsibility to fix bugs correctly. The tests that prove the fix. The honest baselines that show exactly where you stand.
I didn’t know if I could do it well.
Turns out the question wasn’t whether I had the knowledge. It was whether I had the time and the right co-pilot to close the gap between knowing and building.
The PR I submitted is still open. I hope it gets merged. But I stopped waiting.