Overlap
- TypeScript
- Turborepo
- Durable workflows
- Drizzle
- Postgres
- Vercel
Git is built for merging into a mainline, not for knowing what other branches are touching right now. So conflicts get found late, at the pull request, once both sides have diverged enough to make the merge painful. Running several agents in parallel makes it worse: more branches, more commits, more chances two of them are quietly rewriting the same file.
Overlap watches the active branches on a repo and compares which files each one is changing. When two branches start overlapping it says so, early, while the divergence is still small, and it grades how bad it is by how many files are involved.
The clearest picture of why it exists is a pull request where GitHub says there are no conflicts and merging can proceed automatically, while Overlap says three other branches are editing these same files right now. Both are correct. Git is answering a question about the base branch at merge time; Overlap is answering one about the work in flight.
It started as three deployed services and a worker that never stopped running, which is a strange shape for something that only has work to do when someone pushes. It is now one app: the API folded into the web server, and the worker replaced by durable workflows that wake on a webhook and go back to sleep. Postgres with Drizzle, on Vercel and Neon.
What I decided
- A durable workflow instead of a job queue
- The old version ran two independent queues: one synced a branch’s changed files, one compared them. Nothing ordered them, so the code had a five second delay and a comment hoping the sync finished first. If it didn’t, detection read a stale file list and reported the wrong overlaps, silently. Rewriting it as a durable workflow made that ordering a real guarantee rather than a hope, and deleted the delay. That is the whole reason I picked workflows over a queue: two queue topics would have reproduced the same race.
- I rebuilt the database instead of migrating it
- Almost every table here is a cache of something GitHub already knows: repositories, branches, which files a branch touched, which overlaps exist. Only a handful of rows were actually mine. So when I moved hosts I created an empty schema and let the app repopulate itself from GitHub instead of restoring a dump. It made the cutover its own test: if the pipeline rebuilt the same branches and overlaps the old host was showing, the migration worked. It also meant I didn’t carry over any rows that a bug had already corrupted.
- The move off Railway had to actually save money
- The thing I was paying for was an always on worker sitting idle most of the day. I nearly replaced a five dollar a month worker with a ten dollar a month database, because the free credit on the host I picked first was already spent on another project. Checking that before committing turned a migration that would have cost more into one that costs less. Now the compute scales to zero between webhooks, which suits an app that only wakes up when someone pushes.
- What the rewrite surfaced was worse than what it changed
- Porting the worker forced me to read error handlers I had not looked at in months. One caught a failed GitHub call, set the file list to empty, then deleted every tracked file for that branch, so a transient outage quietly erased a branch’s index and made real overlaps disappear from the UI with no error anywhere. Another swallowed check run failures entirely. Neither would have shown up as a bug report, only as Overlap being mysteriously wrong sometimes. Rewriting something is the most reliable way I know to find the parts you stopped questioning.