Skip to content
g! grite refs/grite/wal grite init

← Back to field notes

Why your repo is a better issue store than your tracker SaaS

Neul Labs · ·
architecturestorage

The default architecture of an issue tracker is so old that most engineers stop seeing it as an architectural decision at all. You have a hosted database, somewhere else. You have a web UI on top of it. You have an API that pretends to be the public face of the database, with auth and rate limits. You point your repos at the tracker through reference numbers and webhooks. You pay annually.

If you stop and squint at that arrangement for thirty seconds, almost every property of it is a downgrade from what your repository already provides. We have been using the inferior storage layer for a long time because the better one was not obviously available. Grite is what it looks like when you stop pretending the repo is not a database.

Your repo is already a distributed log

Git is, structurally, a content-addressed append-only log with a graph of references. Every commit is immutable, every object is identified by its hash, and the entire history is independently verifiable. The community spent two decades hardening that data structure. Most production engineers trust it more than the relational database they use for application state.

The thing nobody quite admits is that this is exactly the data structure you would design if you were building an issue tracker from scratch in 2026. You want append-only because issues have an audit trail. You want content addressing because you want tamper-evidence. You want graph references because issues have dependencies. You want replication via standard tooling because every machine on the team is going to have a copy. Git already does all four.

What grite does is add a single ref namespace — refs/grite/wal for the event log, refs/grite/snapshots/ for periodic snapshots, refs/grite/locks/ for distributed leases — and a small materialization layer on top. The actual storage decisions were made years ago by the git authors. We just stopped pretending the tracker needed its own.

Sync is a problem we already solved

The most expensive thing about hosted issue trackers is the part nobody talks about: synchronization. When two people edit the same issue at the same time, the tracker has to pick a winner. The way it picks is usually some combination of optimistic locking, last-write-wins, and silent overwrites. The way you find out is when the field you set is mysteriously the other value next time you load the page.

Two engineers editing the same file in git is a problem with a well-understood solution: a three-way merge driven by a common ancestor. For text edits the merge is usually automatic, and for the cases it cannot resolve, the tools make the conflict visible and let humans decide. The community considers this normal.

The grite WAL does not need three-way merges because events do not conflict. Two appends are commutative — you keep both. Two label additions are commutative — the set absorbs both. A status change uses last-write-wins on the actor’s local logical clock, but the change is preserved in the event log so the prior state is recoverable. Two CRDTs merged in either order produce the same materialized view. This is a 1990s computer science result that hosted trackers do not benefit from because their storage layer is the wrong shape.

The practical consequence is that grite sync does the same merge that git fetch && git push does, plus a CRDT pass over the new events. Two engineers working offline on the same issue do not produce a conflict on rejoin. Two agents racing each other to update the same label end up with both labels applied. The thing hosted trackers spend a lot of engineering time on is something grite does not have to spend engineering time on, because git already did the hard part.

Backups, audit, and the boring property of being in the repo

The boring properties of git-as-storage are the ones that change daily operations the most.

Backup is automatic. Every clone is a full backup of every issue. Every push replicates the issues to the remote. If your remote dies, the issues are still on every developer machine. There is no backup runbook to maintain, because the backup is what every developer already does.

The audit trail is the WAL. Every event is content-addressed, optionally signed, and immutable. “When was this issue closed and by whom” is a query against the event log, not a hopeful read of the application’s change history table. Compliance teams have started asking for cryptographic provenance on agent-driven changes; grite has it because the storage layer makes it free.

The export surface is git. You do not have to fight a vendor for your data back. You already have it. Every clone is the export.

And the part that took us the longest to appreciate: the issues come with the code. When you git checkout a feature branch from six months ago, you also get the issues that were open on that branch. When you tag a release, you tag the state of the queue at the time. The repository stops being a snapshot of just the code and becomes a snapshot of the project, which is what it should have been all along.

The wins for AI agents and small teams

Two populations get the most lift from this architecture. The first is AI coding agents, for the reasons we covered in a separate post: persistent memory, deterministic coordination, machine-readable state, and no API to manage. The second is small teams, for reasons that are even more pragmatic.

A four-person startup running on git does not want to pay for a hosted tracker. They especially do not want to provision accounts, manage seats, configure SSO, and rotate API tokens for a tool whose job is to remember bugs. The friction tax of those operations adds up to a measurable fraction of an engineer per quarter, before you have even gotten to the subscription cost.

A four-person startup that adopts grite has zero accounts to manage. Every engineer who can clone the repo can read and write issues. The tracker is bound to the same access control the repo already has. There is no separate auth story. There is no separate billing story. There is nothing to migrate when the team grows or the founder pivots.

The medium-sized version of the same observation is that grite scales sideways with the repo. If your repo has read access for a contractor, the contractor gets the issues. If your repo is private to your team, the issues are private to your team. Permissions follow the storage, which is the property the hosted version had to bolt on with a separate model.

Where this architecture genuinely loses

It would be dishonest not to name the places git-as-storage costs you something.

A public inbound surface is not native. If you need anonymous bug reports from the internet, you either run a web form that pipes into grite, or you keep GitHub Issues for that surface and use grite for the internal queue. Both work, and most teams pick the second.

A rich web UI is not in scope. Grite has a CLI and exports. If your workflow needs a roadmap view, a kanban board, or cycle reports, those exist as separate tools that consume the exports, and we are agnostic about which one you pick. The point is that the storage is open enough to fit under whichever view you want.

Some integrations were built for hosted trackers and have not been rebuilt for grite yet. Slack notifications, JIRA syncs, and project-management dashboards typically assume an API on the other side. The pattern that works today is a small shell script that watches the WAL and emits the events your downstream needs. That is more code than a managed integration, but less code than you think — and it does not break when the vendor changes their schema.

The thesis, restated

The repository is, structurally, the best place to store the state of a software project. It is replicated, versioned, content-addressed, signed-by-default, permission-aware, and developer-trusted. Everything you want from an issue tracker — durability, audit, sync, offline access, multi-author merging — falls out of properties git already has.

We have used hosted trackers for fifteen years because the alternative was not obvious. The alternative is now obvious. Your repo is a better issue store. Grite is the layer that lets you treat it that way.