# Epiq keeps its issue tracker in a Git-backed event log you can replay

> Epiq is a self-hosted issue tracker that persists every change as an immutable event log synchronised through Git, which is what makes workflow replay possible, since you can play the board back to see what moved when. It links commits to tickets with an Epiq-Ref line, and it ships a terminal interface, a browser GUI and an MCP server.

**ljtn/epiq** — Distributed, code-native issue tracker - audit workflows via time-travel

- Repository: https://github.com/ljtn/epiq
- Website: https://ljtn.github.io/epiq/
- Stars: 549 · Forks: 22
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/ljtn-epiq

## The event log is the design, and everything else follows from it

Epiq is a self-hosted issue tracker that allows you to review workflows in real time or after the fact via replay, and it persists state as an immutable event log, versioned and synchronized via Git. The summary line is blunt about it: all state is kept local, Git-backed, and versioned.

The README's framing of the problem is about where issue trackers live. Most issue trackers live outside your workflow. Instead of a centralized, managed service, Epiq offers issue tracking as an integrated part of the development environment offering workflow replay and inline code-diffs, tackling the review bottleneck with time-travel, filtering, and a tight code integration.

Once state is an append-only event log rather than a row in a table, several things become possible that a conventional tracker cannot offer. You can play the board back, because the log is the board. You can ask what moved when and who moved it, because every transition is an event with an author and a timestamp. And you can filter by history rather than by current state.

The audit section makes the motivation concrete, and it is about agents rather than about humans. Agents now run whole sprints unattended. Because state is an event log, you can replay the board to find out what moved when, who moved it, and what changed along the way. That is a different question from the one a normal tracker answers, and it is the question that appears when a machine is doing the work.

The design list spells out the consequences. Workflow replay, to inspect what happened while you were away, as if it happens now. Simple setup, with no accounts, SaaS or external services required. Repo-native, so your issues can live where your code lives. Offline-friendly, working anywhere with eventual consistency. Fast, local first, and eventual consistency makes Epiq edits instant. Portable, running on your local machine, on a remote Linux server or your grandma's connected toaster. And command driven, scriptable and automation-friendly, ready for the agentic era.

The primary language is TypeScript, the licence is MIT, the default branch is main, and the last push was on 2026-10-20. The recent releases are v1.13.2 from 2026-10-19, v1.13.1 from the same day and v1.13.0 from 2026-10-13, so this is a project shipping several times a week.

## Linking commits to tickets, and how your merge strategy decides whether the link survives

The commit linking mechanism is one line in a commit body, and the README is unusually clear about the consequences of how you merge.

Link a commit to a ticket with an Epiq-Ref: line in its body, naming the ticket's ref:

```bash
git commit -m "fix the parser" -m "Epiq-Ref: 1YRTG8T"
```

The format is forgiving. One line can name several tickets, as in Epiq-Ref: 1YRTG8T, 4KQ2M7D. It can sit anywhere in the body, and its case does not matter. And prefixing the subject with the ref works too:

```bash
git commit -m "1YRTG8T fix the parser"
```

Linking makes a commit show up in the ticket's Code tab, with its message. You can comment on selected lines, and also file a new ticket straight from the selection. And the MCP exposes the ref as a ref property agents can refer to.

The merge rules are the part that catches people, and the README separates the two forms.

Epiq-Ref lines survive a squash merge that keeps the squashed messages in its body, as git's and GitHub's default messages do, so every ticket they name stays linked. A rebase or merge commit keeps them as they are.

Subject prefixes need the commits to land as they are, so you rebase-merge them. Squashing within one ticket's commits is fine as long as the result keeps the prefix, and you should not squash commits carrying different prefixes into one.

That is a real operational constraint on the team rather than on the tool. If your repository uses squash merges with the default message, the Epiq-Ref form works and the subject prefix form silently stops linking. If your repository rebase-merges, both work, and you can use the shorter subject form.

The ticket refs themselves look like short base-32-ish identifiers, 1YRTG8T and 4KQ2M7D in the examples, which is the same shape GitHub uses for its own issue references, so the two are visually similar even though the systems are separate.

## Connecting other repositories, and the safety rules around it

A board can read commits from other repositories, which is described as handy when the board lives in a repo of its own. You can connect one from the GUI, through the Repos section of the settings panel behind the gear, or through Connect repo in the palette, from the TUI with :config repoConnect <url> and :config repoDisconnect <name>, or through the MCP with epiq_repo_connect and epiq_repo_disconnect.

The connection procedure has an unusual two-step shape, and it is deliberate. Connecting is a board-wide action: every contributor's epiq downloads the repo on their next sync, so it takes two steps. The first clones the repo into a staging area and shows what it is and what it costs, its size, host, default branch and latest commit, and how many of its commits name tickets on this board, where none suggests the wrong repo, and writes nothing. Only a confirm connects it, with the TUI taking the same line again with confirm and the MCP a second call with confirm: true.

That preview step is doing two jobs at once. It tells the user whether they pointed at the right repository, and it shows the disk cost before anyone commits to it.

The options are described in detail. branches is default, the default, or all. since is how much history to clone: board, since the board was created, which is the default, all, or a date. label is what the repo is called on the board, its URL's basename unless set, which is how a fork and its upstream are told apart.

The list is part of the board. Connecting, changing and disconnecting are board events, so they sync like any other change and never conflict, nothing to commit.

Then the resource limits, which are unusually specific for a tool like this. Epiq keeps its own read-only bare clone of each repo under ~/.epiq-global/repos/, fetched on sync with your usual git credentials, ssh-agent or a credential helper, and a passphrase prompt cannot be answered, so it is never fetched on startup. A commit shows once it is pushed and the board has synced. A board reads at most 20 repos, and one connected past that is listed but not fetched until one ahead of it is disconnected. Each clone may take at most 2 GB of disk on your machine, and :config repoCloneMaxMb <MB> changes that in your own config. A fetch that would pass it is stopped and undone, and the repo says so.

The security rules close the loop. Since anyone on the board can connect a repo, URLs are read with care: only https, http, ssh, git and user@host:path URLs are accepted, never one carrying a password or a user before an http host, and git runs in the clones with none of your hooks, attribute drivers or command-running transports.

## Installing it, and the two interfaces over one engine

There are two installation routes and both are short.

The binary installer is:

```bash
curl -fsSL https://raw.githubusercontent.com/ljtn/epiq/main/install.sh | sh
```

It installs to ~/.local/bin by default, and you can override that with EPIQ_INSTALL_DIR or XDG_BIN_HOME, and pin a version with EPIQ_VERSION=v1.0.0.

The npm route is:

```bash
npm install --global epiq
```

And to check either one:

```bash
epiq --version
```

Getting started takes two steps. Make sure you are inside a Git repository, running git init if needed, and for collaboration use a repo with a remote. Then run:

```bash
epiq
```

If it is your first run, this opens the interactive setup wizard that sets you up in about 30 seconds.

The browser interface is a separate command:

```bash
epiq gui
```

and on a machine with no display, or with --no-open, it serves without opening a browser, and you open the URL it prints.

The two interfaces are worth understanding as one design. Epiq originated from the command line and offers a first-class terminal experience, but also features a browser interface powered by the same Git-backed event engine. The screenshot in the README shows the terminal UI with the same board rendered in a terminal, with the command palette along the bottom, so the two views are not a thin wrapper over each other; they are two renderings of the same event stream.

The package.json confirms the shape of the distribution. Two binaries are published, epiq pointing at dist/index.js and epiq-mcp pointing at dist/mcp.js, so the Model Context Protocol server ships as a separate entry point in the same package. The engines field asks for Node 18 or newer, the package is ESM, and the build uses esbuild with the CLI and the MCP server as separate entry points, bundled with external packages left unbundled.

There is also a script for building a single executable, build:sea and build:binary, which points at a different artefact format, and the npm build script writes a version file and a skill file before bundling. The presence of write-skill.mjs is a hint that the tool ships an agent-facing skill definition alongside the binary.

## Where Epiq is the wrong tool

Three limits, and the first is about who can actually use it.

The first is that the whole model presumes Git and a terminal. Setup requires being inside a Git repository, and the issue data lives in the repository's history. A colleague who does not have Git installed, or who works in a browser all day, cannot use this without installing and learning a command line tool. That is a narrower audience than a hosted tracker, and for a team with mixed roles it is the constraint that matters most, because an issue tracker nobody can open is an issue tracker people stop filing in.

The second is the eventual consistency model, and it cuts both ways. Edits are instant locally because the change is local first, and they reach other contributors when they sync. That is excellent for offline work and for a single developer, and it means there is no single source of truth at any instant. For a board where two people work on the same ticket, there is a window where their views differ, and the README is honest that this is the design rather than a defect.

The third is the connected-repository blast radius. Every contributor's epiq clones each connected repo on their next sync, and a clone can take up to 2 GB on a machine. The limits are sensible, twenty repos and 2 GB each, and the pre-flight preview before connecting is good design, but a board that connects the wrong repository still causes every contributor to download it. The safety rules around URLs and running git with none of your hooks or command-running transports reduce the risk of the clone itself, but they do not reduce the disk cost.

The alternative approach is a hosted tracker with an API. The difference in approach is that a hosted tracker gives you a single authoritative view, role-based access for people without Git, and a support team behind it, at the cost of a service dependency, a data export problem when you leave, and no history of who moved what. Epiq offers the opposite trade: the history and the repo-native location are the reasons to use it, and the single-authoritative-view and the accessibility are what you give up.

There is also a middle path that the design points at without saying so. The MCP server, published as its own binary, means agents can drive the board, so the humans who do not use the terminal can be given a workflow that an agent mediates. Whether that is enough depends on whether your organisation is willing to make agents part of the issue workflow, which is the same bet the README's audit section is making.

## Conclusion

Adopt Epiq if your team wants an issue tracker that lives in the same Git repository as the code and needs an audit trail of how work moved, because the event log is what makes replay and per-contributor history possible without a hosted service. Do not adopt it if you need issue tracking to be reachable by people without Git or a terminal, because the setup assumes a Git repository and the interface is terminal first. Verify first that your merge strategy preserves the Epiq-Ref lines, since a squash merge that keeps the messages preserves the links and a rebase-merge is needed for subject prefixes, that the connected repositories you point it at are ones you want every contributor cloning, and that you accept the eventual consistency model, where an edit is instant locally and visible to others after a sync.

## FAQ

### What is Epiq and how does it store its state?

It is a self-hosted issue tracker that persists state as an immutable event log, versioned and synchronized via Git, with all state kept local and Git-backed. That design is what allows the board to be replayed to see what moved when and who moved it.

### How do I link a commit to an Epiq ticket?

Add an Epiq-Ref: line naming the ticket's ref in the commit body, for example Epiq-Ref: 1YRTG8T. One line can name several tickets, it can sit anywhere in the body, its case does not matter, and prefixing the commit subject with the ref works too.

### Does my merge strategy affect Epiq commit links?

Yes. Epiq-Ref lines survive a squash merge that keeps the squashed messages in its body, as git and GitHub defaults do, and a rebase or merge commit keeps them as they are. Subject prefixes need the commits to land unchanged, so you rebase-merge them.

### How do I install Epiq?

With the binary installer, curl -fsSL the install.sh and pipe it to sh, which installs to ~/.local/bin by default and can be redirected with EPIQ_INSTALL_DIR or XDG_BIN_HOME, or with a version pinned through EPIQ_VERSION. There is also an npm route with npm install --global epiq, and epiq --version verifies the installation.

### Can Epiq read commits from other repositories?

Yes. A board can connect other repositories, which is useful when the board lives in a repo of its own. You connect from the GUI settings, the TUI with :config repoConnect, or the MCP with epiq_repo_connect, and a preview shows the repo's size and how many of its commits name tickets before anything is written.

### What interfaces does Epiq provide?

A first-class terminal experience, since the project originated from the command line, and a browser interface powered by the same Git-backed event engine, started with epiq gui. The package also ships a separate epiq-mcp binary exposing the Model Context Protocol, and the repo's package.json declares Node 18 or newer as the engine requirement.

## Sources

- [License: MIT](https://github.com/ljtn/epiq/blob/main/LICENSE)
- [ljtn/epiq on GitHub](https://github.com/ljtn/epiq)
- [Project website](https://ljtn.github.io/epiq/)
- [README](https://github.com/ljtn/epiq/blob/main/README.md)
- [Releases](https://github.com/ljtn/epiq/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ljtn-epiq
