{"slug": "show-hn-beaver-backlog-an-issue-tracker-that-lives-in-your-repo", "title": "Show HN: Beaver Backlog, an issue tracker that lives in your repo", "summary": "Beaver Backlog, a local-first issue tracker that stores issues as Markdown files inside a project's repository, has been released as an open-source tool by developer Stefan (builtbystef). The tool requires Go 1.26 or later and can be installed via `go install github.com/builtbystef/beaver-backlog/cmd/beaver@latest`. It provides a CLI and a web UI for managing issues without an external service, account, or database, and is designed to be agent-friendly for coding agents.", "body_md": "**A local-first issue tracker that stores issues as Markdown files inside your project.**\n\nHumans and coding agents coordinate work through the files themselves.\n\nNo external service, account, or database needed.\n\n[Install](#installation) ·\n[Quick start](#quick-start) ·\n[Web UI](#the-web-ui) ·\n[Screenshots](#screenshots) ·\n[Commands](#commands) ·\n[Agents](#for-scripts-and-agents) ·\n[Docs](#documentation)\n\n``` bash\n$ beaver create \"Login form rejects valid passwords\" --label bug --priority high\nCreated ix2guj  Login form rejects valid passwords\n  .beaver/issues/ix2guj-login-form-rejects-valid-passwords.md\n```\n\nMost issue trackers live outside the codebase, behind a web app and an API.\nBeaver Backlog keeps project work *in* the repository, as plain files that travel\nwith your code:\n\n**Markdown-first**: every issue is a human-readable`.md`\n\nfile with a small YAML header. Read it, edit it, diff it, and review it like any other file.**Local by default**: issues live on your disk, in your project.** Version-control-friendly**: plain text that Git diffs and merges cleanly.** Agent-friendly**: coding agents read, create, and update issues through the same files as you.** Nothing hidden**: the files are the only source of truth. The CLI and the web UI are thin clients over them; hand-editing an issue file is a first-class operation.\n\nWith Go 1.26 or later:\n\n```\ngo install github.com/builtbystef/beaver-backlog/cmd/beaver@latest\n```\n\nOr build from a clone:\n\n```\ngit clone https://github.com/builtbystef/beaver-backlog.git\ncd beaver-backlog\ngo build ./cmd/beaver\nbash\n$ beaver init\nInitialized empty Beaver Backlog store in /home/you/project/.beaver\n\n$ beaver create \"Login form rejects valid passwords\" --label bug --priority high\nCreated ix2guj  Login form rejects valid passwords\n  .beaver/issues/ix2guj-login-form-rejects-valid-passwords.md\n\n$ beaver list\nID      PRIORITY  STATE  ASSIGNEE  LABELS  TITLE\nix2guj  high      todo   -         bug     Login form rejects valid passwords\n\n$ beaver start ix2guj\nStarted ix2guj (claimed for stefan)\n\n$ beaver note ix2guj \"root cause: form strips ! before hashing\"\nAdded note to ix2guj as stefan\n\n$ beaver update ix2guj --priority urgent --label regression\nUpdated ix2guj\n\n$ beaver done ix2guj\nMarked ix2guj done\n```\n\nState changes are verbs of their own (`start`\n\n, `done`\n\n, `cancel`\n\n, `reopen`\n\n);\nevery other field — title, description, assignee, priority, labels,\nrelationships — changes through `beaver update`\n\n.\n\n`beaver serve`\n\nstarts a local web UI on loopback over the same files. It uses\nport 2328 by default and scans forward if that port is occupied (override it\nwith `--port`\n\n). No daemon and no build step; stop it with Ctrl-C.\n\n**Board**— issues as cards in state columns; drag a card to move it.** List**— the same issues as a table, sharing one filter bar with the board (label, priority, assignee, text search).** Graph**— the dependency graph as a server-rendered picture: layered layout, parent clusters, dependency arrows; pan, zoom, and filter it.**Issue pages**— rendered Markdown descriptions and notes, every field editable, plus creating new issues in the browser.** Doctor**— store health as a page, with the same safe repair as`doctor --fix`\n\n.\n\nOpen pages notice when the store changes underneath them — a pull, a hand\nedit, another actor — and redraw themselves. Every control is a plain HTML\nform first, so the UI keeps working with JavaScript disabled; scripts only\nadd polish on top. The UI follows your system's light or dark theme, and\nwrites are attributed just like CLI writes (`beaver serve --as <actor>`\n\n).\n\n| The board | The graph |\n|---|---|\nThe issue list |\nAn issue page |\nDark mode: board |\nDark mode: graph |\n\nEach issue is one file in `.beaver/issues/`\n\n, named `<id>-<slug>.md`\n\nfor\nreadability. The short random ID is the identity; the slug just mirrors the\ntitle.\n\n```\n---\nid: ix2guj\ntitle: Login form rejects valid passwords\nstate: done\nassignee: stefan\npriority: high\nlabels:\n  - bug\ncreated: 2026-07-06T20:28:40Z\nupdated: 2026-07-06T20:28:59Z\n---\n\nWhen a user submits a correct password containing a `!`, the form clears and\nshows \"invalid credentials\". Expected: the login succeeds.\n\n## Notes\n\n**stefan** — 2026-07-06T20:28:59Z\n\nroot cause: form strips ! before hashing\n```\n\nThe frontmatter is machine-owned (Beaver Backlog keeps it formatted,\nand unknown keys you add by hand are preserved verbatim, never interpreted);\nthe issue body is yours (Beaver Backlog only ever appends notes to it). State is one of\n`todo`\n\n, `in-progress`\n\n, `done`\n\n, or `cancelled`\n\n— cancelled meaning deliberately\nabandoned, kept visible so nobody re-files it. Any state may move to any\nother; the tracker records reality rather than enforcing a workflow.\n\nFourteen of them, and they fit on one screen:\n\n| Command | What it does |\n|---|---|\n`beaver init` |\nInitialize a store in the current project |\n`beaver create \"<title>\"` |\nCreate an issue (`--body` , `--body-file` , `--label` , `--priority` , `--depends-on` , `--parent` ) |\n`beaver list` |\nList issues (`--state` , `--ready` , `--blocked` , `--label` , `--priority` , `--assignee` , `--parent` , `--search` ) |\n`beaver show <ref>` |\nShow an issue, including what it waits on and whether it is ready |\n`beaver start <ref>` |\nMove to in-progress, auto-claiming if unowned |\n`beaver done <ref>` |\nMark done |\n`beaver cancel <ref>` |\nDeliberately abandon (terminal, but not completed) |\n`beaver reopen <ref>` |\nReturn an issue to todo, clearing its assignee |\n`beaver update <ref>` |\nChange any non-state field (see below) |\n`beaver note <ref> \"<text>\"` |\nAppend an attributed, timestamped note |\n`beaver delete <ref>` |\nDelete the file (for junk; the VCS keeps history) |\n`beaver doctor` |\nCheck store health; `--fix` repairs what is safe to repair |\n`beaver serve` |\nServe the local web UI on loopback until interrupted (`--port` , `--as` ) |\n`beaver whoami` |\nPrint the actor you resolve as |\n\n`update`\n\ntakes as many fields as you like in one invocation, and writes\nnothing at all if they net out to no change:\n\n| Flag | What it changes |\n|---|---|\n`--title <text>` |\nThe title, renaming the file to the fresh slug (the ID is fixed) |\n`--body <text>` / `--body-file <path>` |\nThe description, leaving the `## Notes` section untouched |\n`--assignee <actor>` / `--unassign` |\nThe assignee |\n`--priority <level>` |\nPriority (`urgent` –`low` , or `none` to clear) |\n`--label <spec>` |\nLabels: `bug` or `+bug` adds, `-bug` removes; repeatable, CSV |\n`--depends-on <spec>` |\nBlocking edges, by ref, with the same `+` /`-` syntax |\n`--parent <ref>` / `--no-parent` |\nThe parent issue |\n\nA `<ref>`\n\nis an issue's ID, its slug, or its file name — resolved by exact\nmatch only, never by prefix or fuzzy match. Run `beaver help`\n\nfor full usage.\n\nOutput format auto-detects: human-readable tables on a terminal, JSON when\npiped (override with `--format human|json`\n\n). Exit codes are stable — `0`\n\nsuccess, `1`\n\nruntime failure, `2`\n\nusage error, `3`\n\nissue or store not found.\n\nEvery mutation is attributed to an **actor** — a free-form name; humans and\nagents are treated identically. Identity resolves from `--as`\n\n, then the\n`BEAVER_BACKLOG_ACTOR`\n\nenvironment variable, then per-machine user config (a\nhuman is prompted once, in a terminal). Set `BEAVER_BACKLOG_ACTOR`\n\nin an agent's\nenvironment and every claim and note it makes is attributed correctly.\n\nA complete issue — title, description, and metadata — is one command: pass a\nshort description inline with `--body`\n\n, or pipe multi-line Markdown through\n`--body-file -`\n\n(a path works too) and skip the shell quoting:\n\n``` bash\n$ beaver create \"Login form rejects valid passwords\" --label bug --priority high --body-file - <<'EOF'\nWhen a user submits a correct password containing a `!`, the form clears and\nshows \"invalid credentials\". Expected: the login succeeds.\nEOF\nCreated t4y1gv  Login form rejects valid passwords\n  .beaver/issues/t4y1gv-login-form-rejects-valid-passwords.md\n```\n\nRoutine upkeep is likewise one command rather than a sequence — `update`\n\ntakes\nevery field it changes at once, and reports the result in the same single-issue\nJSON shape the lifecycle verbs use:\n\n``` bash\n$ beaver update t4y1gv --priority urgent --label regression,-needs-triage --assignee agent-7\nUpdated t4y1gv\n```\n\nNothing here needs a terminal or an `$EDITOR`\n\n. To rewrite a description,\n`beaver update <ref> --body \"<markdown>\"`\n\nreplaces it and leaves the\n`## Notes`\n\nsection byte-identical; `--body-file <path>`\n\nreads it from a file,\nand `--body-file -`\n\nfrom stdin, which is the way to send multi-line Markdown\nwithout shell quoting:\n\n``` bash\n$ beaver update t4y1gv --body-file - <<'EOF'\nSubmitting a correct password containing a `!` clears the form and shows\n\"invalid credentials\". Expected: the login succeeds.\nEOF\nUpdated t4y1gv\n```\n\n**Editing the issue file directly is equally first-class** — the files are the\nsource of truth, and that is the interactive path this tool deliberately does\nnot wrap in a command. Three rules keep a hand edit safe:\n\n**Leave the** It is the append-only coordination journal; rewriting or dropping another actor's entries breaks the contract. Edit the description above it, and add your own entries only through`## Notes`\n\nsection alone.`beaver note`\n\n.**Never change the**— it is the issue's identity, and the filename merely mirrors it.`id`\n\nfield**Follow up with** A hand edit alone does not bump the`beaver note <ref> \"<what you changed>\"`\n\n.`updated`\n\ntimestamp; a note both journals the change for other actors and bumps it.\n\n`beaver doctor`\n\nis the net under all of it: run it after a hand edit and it\nreports anything the edit left behind. Whatever drifts anyway — say a filename\ngone stale after a hand-retitle — is lint, not corruption, and\n`doctor --fix`\n\nrepairs it.\n\nBeaver Backlog is local-first and has no sync layer, so it cannot *lock* an\nissue. Instead an actor **claims** one by setting its `assignee`\n\nfield, and\nthat claim travels through the VCS like any other change. It is a signal, not\na rule: two actors on different branches can claim the same issue, and the\nmerge surfaces the clash. Push claims early and integrate often.\n\nGive each concurrent agent its **own working tree** (a separate `git worktree`\n\nor clone). Two agents sharing one checkout can silently overwrite each other's\nedits — that configuration is unsupported.\n\nIssues relate through `depends_on`\n\nand `parent`\n\n, stored one-sided on the\ndependent or child. `beaver list --ready`\n\nshows what is actionable now (todo,\nevery dependency done); `--blocked`\n\nshows what is waiting.\n\n`beaver init`\n\nwrites `.beaver/config.yml`\n\n, which is committed and shared like\nthe issues. It records the store's format version and nothing else today.\nBeaver Backlog only ever reads and writes files — it never runs a\nversion-control system, so committing your issue files stays entirely yours to\ndo (and stays free to bundle with the code the issue produced).\n\nYour identity lives in per-machine user config, never in the repository.\n\nDistributed, hand-editable files can drift: filenames out of sync with titles,\ndangling references after a bad merge, dependency cycles, typo'd frontmatter\nkeys. Everyday commands degrade gracefully — an invalid file is skipped with a\nwarning, never a crash — and `beaver doctor`\n\nreports everything it finds.\n`doctor --fix`\n\nrepairs only what is unambiguous (like drifted filenames) and\nnever removes data.\n\n— the project's language: what an issue, actor, claim, and note precisely mean.`docs/GLOSSARY.md`\n\n— the conventions this repository uses to track its own work with Beaver Backlog.`docs/TRACKER.md`\n\n— the modules and the seams between them.`docs/ARCHITECTURE.md`\n\n— the coding and test conventions used by contributors.`docs/CODING_STANDARDS.md`\n\n— the architecture decisions behind the design and their tradeoffs.`docs/adr/`\n\nBeaver Backlog is now at **1.0.0**. It will continue to evolve, but compatibility\nmatters: changes to the issue file format, commands, flags, machine-readable\noutput, and other public behavior will aim to remain backward-compatible.\nUnavoidable breaking changes may still happen, but they will be kept to a\nminimum and called out clearly.\n\nContributions are welcome — see [ CONTRIBUTING.md](/builtbystef/beaver-backlog/blob/main/CONTRIBUTING.md) for how\nto build, test, and submit changes.", "url": "https://wpnews.pro/news/show-hn-beaver-backlog-an-issue-tracker-that-lives-in-your-repo", "canonical_source": "https://github.com/builtbystef/beaver-backlog", "published_at": "2026-08-11 19:24:17+00:00", "updated_at": "2026-08-11 19:42:39.881192+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Beaver Backlog", "builtbystef", "Go"], "alternates": {"html": "https://wpnews.pro/news/show-hn-beaver-backlog-an-issue-tracker-that-lives-in-your-repo", "markdown": "https://wpnews.pro/news/show-hn-beaver-backlog-an-issue-tracker-that-lives-in-your-repo.md", "text": "https://wpnews.pro/news/show-hn-beaver-backlog-an-issue-tracker-that-lives-in-your-repo.txt", "jsonld": "https://wpnews.pro/news/show-hn-beaver-backlog-an-issue-tracker-that-lives-in-your-repo.jsonld"}}