# Setting up Backlog.md for agents

> How I set up Backlog.md boards for coding agents: the settings that can't change later, the config I hand-edit, the CLI flags that destroy another session's writes, and upstream's traps.

- Canonical page: https://rob-knight.com/articles/setting-up-backlog-md-for-agents/
- Kind: Article
- Published: 2026-10-09
- Updated: 2026-10-19
- Topics: agents

[Backlog.md](https://github.com/MrLesk/Backlog.md) is the default task tracker on my repositories. It keeps each task as a Markdown file under `backlog/` in the repository and drives it from a CLI, so agents read and write it the same way I do and it travels with the code. Most of what I've learned setting it up is about what an agent can break, and nearly all of it is decided in the first command.

This is written against Backlog.md 1.53.0. Several of the behaviours below changed in patch releases, so check `backlog --version` and the relevant `--help` before trusting any of it.

<figure class="diagram figure-breakout">
  <img class="on-light" src="/diagrams/58-tldr-setting-up-backlog-md-for-agents-light.svg" alt="A numbered strip of four columns, following a Backlog.md board from creation to a finished task. One, at init, choose once: --task-prefix with letters only, never the default task; --zero-padded-ids 4, which is not retroactive; and --backlog-dir backlog, which cannot be moved. Two, by hand, config.yml: a Parked status for work that is blocked with a note saying where to resume; definition_of_done set to just check, the real CI gate; and auto_commit false. Three, every edit, add never replace: OK, --append-notes adds to the section; FAIL, --notes replaces another session&#x27;s notes; FAIL, a second task edit replaces --dep, --label and --ref. Four, to finish, one call: --check-ac 1 --check-ac 2 -s Done together, highlighted, so an interrupt cannot leave done work unticked; and FAIL, hand-edited markdown, where a broken marker drops a section with exit 0. A note at the foot: written against Backlog.md 1.53.0, several of these behaviours changed in patch releases." width="1440" height="814" loading="lazy" decoding="async">
  <img class="on-dark" src="/diagrams/58-tldr-setting-up-backlog-md-for-agents-dark.svg" alt="" aria-hidden="true" width="1440" height="814" loading="lazy" decoding="async">
  <figcaption>Upstream's `check_active_branches` default is a fifth choice made at init: left on, an ordinary uncommitted edit can make a task return a 409 while `doctor` reports the board clean.</figcaption>
</figure>

## Three settings you only choose once

The task prefix, the zero-padding width and the backlog directory are fixed at `init`. Pick them before running anything:

```bash
backlog init "<ProjectName>" --defaults \
  --task-prefix <letters> --zero-padded-ids 4 \
  --backlog-dir backlog --config-location folder \
  --agent-instructions agents \
  --check-branches false --include-remote false \
  --install-claude-agent false
printf '@AGENTS.md\n' > CLAUDE.md
```

The prefix is letters only. A digit is rejected, so a short code like `m7c` fails, and `draft`, `doc` and `decision` are reserved. Never keep the default `task`: once an agent pastes an ID from one repository into another, `task-12` could be anything. A real prefix gives `RKB-0012`, which can only mean one board.

Zero padding isn't retroactive. Turn it on after the ninth task and the board carries `ABC-9` and `ABC-0010` side by side forever, sorting wrongly in every listing. The directory can't move either: `init` refuses to relocate it, and my own tooling hardcodes `backlog/docs`.

`--agent-instructions agents` writes upstream's instruction block into `AGENTS.md` between markers, and `CLAUDE.md` becomes a one-line import of it, so Claude Code and Codex both read one file. That block belongs to the tool. Anything repository-specific written inside its markers is gone the next time the block is refreshed, so the repo's own rules sit outside them.

## The config I edit by hand

List values can't be set through `backlog config set`, so `backlog/config.yml` is the one Backlog file I edit by hand:

```yaml
statuses: ["To Do", "In Progress", "Parked", "Done"]
definition_of_done: ["just check"]
auto_commit: false
```

`Parked` matters most. It means attempted, blocked and left with a note on where to resume, which is not the same as `To Do`. A long unattended run produces exactly that state, and if the board flattens it back to `To Do` the resume point is lost and the next run starts from scratch. `Done` has to stay last, because the final status is the terminal one.

<figure class="diagram figure-breakout">
<img class="on-light" src="/diagrams/34-backlog-task-lifecycle-light.svg" alt="A band of four status columns, left to right: To Do, In Progress, Parked and Done. An arrow runs from To Do to In Progress and on to Done, which is drawn last and marked terminal. A branch from that line drops into Parked, and a return arrow from Parked back to In Progress is labelled resume from the note. Below the band, two rows of commands. An OK row: --append-notes adds to the section, and --check-ac 1 --check-ac 2 -s Done in one call. A FAIL row: --notes replaces the whole section, and a second task edit replaces --dep, --label and --ref." width="1440" height="648" loading="lazy" decoding="async">
<img class="on-dark" src="/diagrams/34-backlog-task-lifecycle-dark.svg" alt="" aria-hidden="true" width="1440" height="648" loading="lazy" decoding="async">
<figcaption><code>Parked</code> is the only status with a way back, and the note it keeps is what makes that return cheaper than starting again from <code>To Do</code>.</figcaption>
</figure>

`definition_of_done` carries the repository's real gate, read from its CI rather than guessed. The [v1.53.0 defaults documentation](https://github.com/MrLesk/Backlog.md/blob/fd20f71493fb4eda44b021aa89f257956f17fb71/README.md#definition-of-done-defaults) says new tasks inherit it by default, with `--no-dod-defaults` available to opt out. I keep the gate on the task. `auto_commit` is off because whatever runs the agents owns every commit, and the auto-commit path has had two bugs where it staged more than it should.

## Flags that destroy another session's writes

The [v1.53.0 CLI reference](https://github.com/MrLesk/Backlog.md/blob/fd20f71493fb4eda44b021aa89f257956f17fb71/CLI-INSTRUCTIONS.md) distinguishes setting notes from appending them. `--notes`, `--plan` and `--final-summary` replace the whole section. Each has an `--append-notes` style form that adds to it, and the bare form is the one that bites: two agents on adjacent work, one writes notes, the other sets its notes with the bare flag a minute later, and the first agent's notes are gone. I never use a bare flag that has an append variant, and a global `PreToolUse` hook, `backlog-guard.py`, denies the bare forms in every repository on both Claude Code and Codex.

Set-valued flags have the same problem across calls. A second `task edit` replaces whatever the first one set for `--dep`, `--label`, `--ref`, `--assignee` and the acceptance criteria. Repeating a flag inside one call adds (`--dep A --dep B` gives both); a second call throws the first away. The hook can't see across separate tool calls, so this one relies on agents using the additive forms, `--add-label` and `--add-ref`, or doing everything in one call.

Finishing a task is also one call, so an interrupt can't leave finished work looking unfinished:

```bash
backlog task edit ABC-0007 --check-ac 1 --check-ac 2 -s Done
```

Never hand-edit task markdown. Sections are delimited by HTML comment markers, and a broken marker silently drops the section with exit 0. The data stays in the file until the next CLI write overwrites it, and `backlog doctor` only repairs duplicate IDs, so there's nothing to recover it with. From 1.54.0 the CLI refuses to replace a legacy section whose boundaries are ambiguous instead of deleting what follows it, which stops that overwrite but not the hand edit that caused it. Two agents on one task is the same failure from a different direction: the concurrency fix in 1.50 covers the edit path only, not reordering, draft saves, the web UI or doc updates.

## Upstream traps

- `check_active_branches` defaults to true and breaks ordinary branch work. It decides whether a task is an ambiguous duplicate by content hash, so a task with an uncommitted edit, or a different copy on any active branch, returns a 409 while `doctor` reports the board clean. Saving a task in the web UI is enough to reproduce it. Hence `--check-branches false` at init.
- IDs are sequential and collide across clones and worktrees. Upstream declined a collision-free mode. `doctor` repairs duplicate task IDs only; duplicate doc and decision IDs resolve silently by title order, so retitling a doc can re-point its ID.
- Archiving releases an ID for reuse, and there's no `task delete`, only `archive`, which leaves a file to remove before committing.
- Decisions only have `create` and `list`, with sections only the web UI can fill and no way to supersede one. I keep durable reference in docs and treat tasks as the unit of work.
- [1.54.0](https://github.com/MrLesk/Backlog.md/releases/tag/v1.54.0), released on 16 October, changes the two traps above: archiving keeps the ID, `task delete` removes an archived task for good, decisions gain a status `edit`, `archive` and `delete`, and `task complete` is gone.
- The [v1.53.0 README](https://github.com/MrLesk/Backlog.md/blob/fd20f71493fb4eda44b021aa89f257956f17fb71/README.md) makes CLI instructions the default and says MCP "remains supported" for anyone who prefers a connector, and 1.54.0 still adds MCP operations. The maintainer's own pull requests call it [the legacy MCP adapter](https://github.com/MrLesk/Backlog.md/pull/776), kept in step with a CLI-first workflow, so I don't build on it. My agents use the CLI.
- `backlog config get definition_of_done` answers "Unknown config key" even though the setting works, because the CLI namespace is camelCase. The failing `get` looks like an ignored setting; a throwaway task shows the real Definition of Done.
- A shallow `config.yml` can enlist the board in a repository's own build. A Home Assistant add-on repository discovers add-ons by finding `config.yml` two levels down, so it built `backlog` as a fifth add-on and failed CI with five missing-field errors. I run a `find` for shallow `config.*` files before `init` and use `--config-location root` if anything matches.

## What goes on the board

`backlog/` is committed, so tasks, docs and decisions never carry account identifiers or personal data: no email addresses, handles, account IDs, device names or addresses. Write the shape, not the instance. On a public repository the sweep widens to installation, tenant and stack IDs and internal hostnames, which are unremarkable in a private repository and don't belong on a public board.

Moving a repository off GitHub Issues imports open work as tasks and closed work as one index doc, never as `Done` tasks. Board IDs follow creation order, so they can never match the `#123` numbers already cited in commits and code, and a pile of imported `Done` tasks buries what the board is for, which is what's left to do. An empty board is a legitimate outcome of a migration.

Some repositories stay boardless on purpose. The agent tooling itself, the loop protocol, the transcript catalogue and the pi extensions, is spread across public and private repositories whose tasks usually span a public release and a lock bump somewhere else, so all of it is tracked on one board in a private repository instead of four.

## How a loop uses the board

In a [v3 loop](/reference/loop-contract/) the board is the queue. Before each wave the root runs `loop-lint queue`, which fails if a queued task is excluded by the charter, already `Done` or labelled `needs-owner`. A task the loop can't finish is parked with one of five reasons: `owner`, `authority`, `dependency`, `defect` or `evidence-later`. An `evidence-later` park lands the work and labels the task `verify:<date>` for a check that can only run later. Each landing commit carries a `Task: <ID>` trailer, and the closeout marks every landed task `Done` with `--check-ac` and `-s Done` in one call and appends one note to every parked task.

## What it doesn't solve

The failure I need the guards to catch is an agent stopping partway through finishing a task: the work is done and the box isn't ticked. Upstream's instruction block opens with a `CRITICAL_INSTRUCTION` nudge on every request. That is an instruction, not a measurement of how often an agent follows it. The board is only as current as the hooks and the loop's own closeout make it, and I expect to keep tuning those.
