Nobody has ever asked you to upstream your dotfiles.
For thirty years that was the deal with every tool we touched: if you didn't like it, you changed it, and the change lived with you. Then the tools started writing the code, and the deal quietly ended.
This essay is about why the deal is back on the table. Because the thing that used to make forking expensive — the labour — is exactly what agents just made cheap.
The Workbench Instinct
Show me a developer who has never touched their config and I'll show you someone who hasn't started yet. Vim users brag about their init.lua the way woodworkers talk about a hand plane they've had for twenty years. Emacs people wrote a whole operating system inside a text editor because they could. VS Code won partly because it shipped an extension API and got out of the way. Dotfiles repos are public artifacts, starred and forked, because the setup is part of the craft.
This isn't productivity theatre. Some of it is genuine need, some of it is fixing a specific annoyance that only you have, and a lot of it is just fun. All three are valid. The workbench is where the joy lives — and nobody ever waited for permission to alias a command.
Then the Harness Era Arrived
Then agentic coding tools showed up and quietly changed the shape of the deal.
The best-in-class agent harnesses are increasingly vendor-controlled. Claude Code is a product, not a repo you can clone and rebuild. Google announced it's retiring Gemini CLI in favour of a closed-source successor. And note where the line falls: Codex CLI is Apache-2.0 and sitting right there on GitHub, but the Codex desktop app — the thing most people actually click on — is not. The terminal stayed open. The interface didn't.
Even the tools that are open source are moving fast enough that they aren't taking your patches. T3 Code's README says it plainly: we are not accepting contributions yet.
Now, let me be fair, because the lazy version of this argument gets torn apart in the comments. These tools are not closed boxes. Claude Code has a real plugin ecosystem — skills, subagents, hooks, MCP servers, marketplaces, thousands of community extensions. That's a genuine, generous surface area, and pretending otherwise is dishonest.
But here's the thing that actually matters:
An extension point is something you are granted. A fork is something you take.
A plugin API defines the seams the vendor chose to expose. Inside those seams you have enormous freedom. Outside them you have exactly zero. Take skills, since they're the best example of a well-designed extension point. You can write as many as you want. You cannot change how they get invoked — the matching logic, when the model decides one is relevant, what gets injected and in what order. You cannot change the UI at all: not the composer, not how a diff renders, not where the token counter sits. You can register a hook on PostToolUse. You cannot change how context gets compacted. You can add a slash command. You cannot change what happens when the model returns a malformed tool call at 2 AM on your production repo.
None of that is a complaint about the design. Those are reasonable places to draw a line — someone has to draw one. It's just that they drew it, not you.
Every extension system is a negotiation where only one side writes the contract. That was fine when the tool was a text editor. It's less fine when the tool is the thing writing most of your code.
Forking Was Always the Escape Hatch
None of this is new. The industry's answer to "the vendor won't ship what I need" has always been the same: take the code and go.
- OpenOffice became LibreOffice.
- MySQL became MariaDB.
- Redis's license change gave us Valkey.
- Terraform's gave us OpenTofu.
- LinkedIn ran its own Kafka branch for years because upstream couldn't move at their scale.
- Every serious low-latency shop tunes its own kernel, because the general-purpose scheduler was never built for their tail latency.
But look at what all of those have in common: they required a team. A fork wasn't a decision, it was a headcount. You needed people to maintain it, rebase it, patch its CVEs, and carry it forward forever. So the honest calculation for most of us was: I want this change, but I don't want it enough to own a fork for the next three years. And so we filed a feature request into the void and moved on.
That calculation is what changed. Not the licenses. The cost.
Code Got Cheap
The thing we're all slowly internalising is that the expensive part of forking was never the idea. It was the labour. Reading forty thousand lines of unfamiliar TypeScript to find the one place a menu gets populated. Understanding the build. Writing the tests. Doing the rebase six weeks later.
That's exactly the work agents are now good at. Not the creative part — the archaeology. Point an agent at a repo and ask where the composer's slash menu is assembled, and you have an answer in ninety seconds instead of an evening.
# The old fork
idea → team → maintenance → rebases → three years of your life
# The new fork
idea → agent → a Tuesday afternoon So the fork stops being a strategic commitment and becomes a Tuesday.
None of this removes judgment. The agent does the archaeology; you still decide what's worth changing, and you still own what lands in the repo. This is the same shift I wrote about in From Coders to Owners — the typing got cheap, the responsibility didn't move an inch. A fork just makes that concrete: AI writes the diff, you own the workbench.
What I Shipped Into My Fork
I've been running my own fork of T3 Code. It's public — github.com/arjunagi-a-rehman/t3code — and here's what I've shipped into it, for me:
1. OpenCode slash commands in the composer. The slash menu only surfaced built-ins — /model, /plan, /default. Now it discovers OpenCode custom commands: global ones from the config directory, and per-project .opencode/command/*.md files. A new providers.listProjectSlashCommands RPC resolves them against the thread's cwd — a file scan in local mode, command.list against the server in external-server mode. Project commands override snapshot commands on name clashes, matching OpenCode's own precedence. And a leading /name args on a fresh turn routes through session.command instead of getting shipped off as plain prompt text.
2. A custom app background image. Upload an image, stored on-device as a data URL, with adjustable opacity. The shell surfaces relax into a translucent scrim so it shows through.
3. Provider model filtering with bulk hide/show. Sub-provider filtering and hide-all/show-all controls, because the model catalogs have gotten absurd.
Three features. None of them proposed upstream. None of them waiting on anyone's roadmap. That's the point.
A Feature Does Not Have to Be Useful
Notice something about that list. Item 1 is real engineering. Item 3 is a real annoyance. Item 2 is a background image.
It saves me zero seconds. It ships nothing faster. It would not survive a single sentence of justification in a sprint planning meeting. I built it because I wanted the thing I stare at all day to feel like mine, and looking at it makes me feel good.
That used to be a completely normal reason to change software. Somewhere along the way we started requiring every change to defend itself in front of a productivity tribunal, and I think that's most of why the work stopped being fun.
Nobody asks a Vim user to justify their colourscheme. Nobody demands ROI on a custom prompt in your shell. The rice, the fonts, the transparency, the ASCII art on login — none of it was ever about output. It was about the fact that this is your workbench and you get to decide what it looks like. That instinct is the same one that eventually produces the good stuff, because a person who feels ownership over a tool starts noticing things a user never would.
So: build the useless feature. It's yours. That's the whole reason.
Your Desire Is the Limit
Once the fork is cheap, the constraint moves. It stops being can I and becomes what do I actually want.
Small example that bothered me for years: why can't an Express app produce an OpenAPI definition the way FastAPI does, from the code itself? Why am I hand-writing a YAML file that drifts out of sync by Thursday?
For most of my career the answer was: because that's how Express is. Now the answer is: write the middleware. Or fork Express, add the introspection at the router level, and run your fork. Both are afternoons.
Multiply that by every "why doesn't it just—" you've swallowed in the last five years. That backlog is the real opportunity here.
The Fork Is the Destination
Here is the part I want to be loudest about, because it's the part people get backwards.
Your change does not need to go anywhere.
It doesn't need to be merged. It doesn't need a PR. It doesn't need to be reviewed, approved, blessed, or accepted by anybody. It doesn't need to be good enough for strangers. It doesn't need a use case, a benchmark, or a paragraph explaining why it's valuable to the community.
You wanted something. You made it. You use it. That's the entire lifecycle.
We've somehow absorbed the idea that changing open source software means contributing to open source software — that the only legitimate endpoint for a diff is upstream. It isn't. Upstreaming is one option among several, and for most of what you'll build it's the wrong one. My background image should never be in T3 Code. It was never meant for T3 Code. It was meant for me, and it's already exactly where it belongs: in my fork, on my machine, running right now.
That's not a lesser outcome. That's the normal outcome. The fork is the destination, not a waiting room for the PR.
Nobody has ever asked you to upstream your dotfiles.
Now the Discipline Part
Which brings me to the thing I actually worry about, because "just fork it" curdles into something ugly very quickly.
Do not open a pull request as your first move. Please. Maintainers are drowning. Agents made it dramatically easier to produce a diff and did nothing to make it easier to review one, and that asymmetry is landing entirely on unpaid people. T3 Code, for the record, had hundreds of open PRs while explicitly saying it wasn't taking contributions.
So:
- Default to your own fork. Ship it there. Live with it for a while. Most changes should stop here permanently, and that's fine.
- Read
CONTRIBUTING.mdbefore you consider anything else. Every time. If it says no contributions, believe it. - Open an issue and ask before you build for upstream. Describe the problem, not your solution. Half the time you'll learn there's a reason it works that way.
- Accept that what you want may not be what the project wants. That's not a rejection of you. A project has a scope, and a maintainer defending that scope is doing their job.
Contributing upstream is a generous thing to do when the change genuinely serves everyone and the maintainers have signalled they want it. It is not a tax you owe on every line you write.
And be honest about what a fork costs you, because it isn't free even now:
- You own your security patches. If upstream fixes a vulnerability, you rebase or you're exposed.
- Merge debt compounds. The further your fork drifts, the more each upstream sync hurts.
- Keep your changes small and surgical. A patch that touches three files survives a year of rebases. One that restructures a module dies on the first major release.
- Agent-generated code you haven't read is a liability you're choosing to install into the tool that has write access to all your repositories. Read the diff.
The rule I'd offer: fork freely, keep it, and upstream only when you're sure it's wanted.
Bring Back the Joy
The good news is that the open ecosystem didn't lose. OpenCode is the most-starred harness out there. Pi ships a genuinely minimal, forkable core built on the assumption you'll rewire it. T3 Code puts a real GUI over whichever CLI agent you feel like driving today. The escape hatches exist and they're good.
I've made my own small bet on that openness: the agent stack behind /agent on this site is a few hundred lines of open, forkable, markdown-driven code — I wrote the whole thing up in Software Can Talk. No vendor permission slip required.
What's missing isn't the tools. It's the habit.
We spent a couple of years being consumers of AI tooling — waiting for changelogs, upvoting feature requests, tweeting at vendors, accepting the shape of the thing we were handed. That was a reasonable posture when the cost of doing otherwise was a team.
It isn't a team anymore. It's an afternoon and a bit of nerve.
So stop waiting. Read the contributing guide. Open the issue. Fork the repo. Change the thing that's been annoying you for six months. Put a background image in it for no reason at all.
The workbench was always ours to build.
The Fork, at a Glance
How we went from shaping every tool we touched, to waiting on vendor roadmaps, to an afternoon being enough to take the workbench back.
Comments
Loading comments…