Rohrpost
Why I built a git-native issue tracker for coding agents
Watch a coding agent work on a ticket in a hosted tracker, and you will see it spend more effort on logistics than on code: authenticate to the API, fetch the issue, figure out the branch, remember to update the status, paste links back and forth. The work item lives in one system; the work lives in another. That split is the bug.
The problem: context split across systems
An issue tracker is a database with opinions. Hosted trackers add permissions, rate limits, web UIs, and API tokens — everything a human needs, and everything an agent has to negotiate. Concretely, for an agent to do ticket-driven work the traditional way, it needs:
- Credentials for the tracker, separate from the repository's.
- Network access at exactly the moments it wants to read or update state.
- A mapping between tracker IDs, branches, and commits that exists only in convention.
- Enough patience to render other people's rich-text comments into something it can reason about.
Meanwhile the repository already has a distributed database with atomic commits, offline support, code review, and history. It is called git. The question was not "how do I give agents access to the tracker" but "why is the ticket not in the repository in the first place?"
Design constraints
- Tickets are files in the repository, versioned like code, reviewed like code.
- Every command emits machine-readable JSON — agents are the primary audience.
- A ready queue that respects dependencies and claims, so an agent can answer "what should I do next" without human triage.
- One portable binary, no server, no daemon, no runtime dependencies. Standard library only where possible.
How the pieces fit
Rohrpost keeps its state under .rohrpost/ in the repository. A ticket is a small structured file with an ID, type, status, and dependency edges. Git carries the history; a lifecycle keeps the semantics:
# End to end, without leaving the repository
$ rp init
$ rp new "Fix token refresh race" --type bug
$ rp ready --json
$ rp claim 1a2f
$ rp close 1a2f --reason "done"
Because tickets are files, everything git already does comes free: branches carry in-progress tickets, merges reconcile them, code review includes ticket changes, and cloning gives you the entire work history offline.
JSON on every command
The single highest-leverage decision was making JSON output universal rather than an afterthought. An agent that can parse the ready queue can plan; an agent staring at formatted table output has to scrape prose. Human-readable output is the derived layer, not the other way around.
$ rp ready --json
[
{
"id": "1a2f",
"title": "Fix token refresh race",
"type": "bug",
"status": "ready",
"depends_on": [],
"claimed_by": null
}
]
Dependencies make the queue honest
A work queue that lists everything is a backlog browser, not a queue. Rohrpost's ready queue contains only tickets whose dependencies are closed and which nobody has claimed. Blocked work stays visible through the dependency graph but out of the agent's face. In practice this is what turns "agent generates pull requests" into "agent works through a plan."
Alternatives I considered
GitHub Issues with the CLI. Excellent for open-source coordination, and I still use it for exactly that. But it couples the work database to a hosted service, needs tokens distinct from repository access, and is unavailable offline or behind restricted networks. For agent-driven work in private repositories, the round-trips add up.
TODO comments and markdown checklists. Zero infrastructure, and zero structure: no dependencies, no claims, no lifecycle, no queryable state. Fine for one person's afternoon; not a work system.
A local SQLite tracker. Queryable and structured, but a binary database in git is opaque to review and merge-conflict prone. Text files with a lifecycle beat a database nobody can diff.
Failures and lessons
Failure: freeform status fields. Early on, statuses were arbitrary strings. Agents invent statuses ("in-progress", "wip", "In Progress") and the lifecycle dissolves. A closed set of statuses with explicit transitions fixed it.
Failure: implicit claiming. The first claim mechanism was "assigned in the ticket body," which two agents could both satisfy. Claims became first-class state, which made the ready queue safe for concurrent agents.
Lesson: design for the reader that parses. JSON-first felt backwards for a CLI tool and was exactly right for an agent tool.
Lesson: the repository's existing semantics are a feature list. Branching, merging, review, offline cloning — reimplementing any of these in a tracker is wasted work; inheriting them by storing state in git is free.
Scope honesty
Rohrpost is not a replacement for hosted trackers in organizations that need cross-repository dashboards, permissions management, or product-owner workflows. It is the right tool when the unit of work is "an agent, a repository, and a queue of well-defined tasks." Usage documentation — including the full lifecycle reference — lives on the Rohrpost site.