Deferred tasks for Claude Code, stored as files in your repository.
Claude notices something worth doing later — an unrelated bug, a refactor out of scope — and it lands in the chat, or in memory, or in some stray file. Next session it is gone. There is no one place a deferred task reliably ends up.
This plugin makes one: docs/backlogs/, a markdown file per task, in your repo,
under version control, in a fixed format.
Claude: While fixing the parser I noticed that
/api/syncretries 429s with no backoff — unrelated to this task. Record it in the backlog?You: yes
Claude: recorded in the backlog #1: Retry on 429 in the sync worker
Later, /backlog:list:
# PRI STATUS AGE TITLE
1 high open 0m Retry on 429 in the sync worker
2 medium open 2d Extract form validation into useFormValidation
3 low open 9d Drop the unused legacy exporter
Optionally the number of open tasks sits in your status line: 📋 3.
claude plugin marketplace add SI-IC/backlog-skill
claude plugin install backlog@backlogNo per-project setup — docs/backlogs/ is created on the first record.
The marketplace registers under the name backlog, and Claude Code keeps one
marketplace per name: if you already have a different marketplace called
backlog, adding this one replaces it. Check with claude plugin marketplace list
if the commands do not match this README.
A plugin runs with your privileges, so here is the whole surface, in one place:
python3— the engine is a single stdlib-only script. No pip, no venv, no network access, no daemon.docs/backlogs/in the current repository — the only place entries are written, and writes stay inside the checkout (Limitations).- A lock file in the system temp directory —
backlog-locks-<uid>/<hash>.lockin$TMPDIR, a0700directory of your own, so that two sessions in one checkout cannot claim the same id. The file is empty, exists only while an id is being allocated, and is deleted on release; anything a killed session left behind is swept a day later. settings.jsonandbacklog-statusline.shin your Claude Code config directory ($CLAUDE_CONFIG_DIR,~/.claudeby default) — only if you run/backlog:statusline-install. The settings file is rewritten through a temporary file and an atomic rename; if it is a symlink, the target is written, not the link. The0700wrapper holds your previous status-line command and runs it throughshon every status-line refresh.- Nothing else. No network calls, no telemetry, no background process, no files outside the paths above.
Most "helpful" automation writes things you never asked for. This one records in exactly three cases:
- You asked — "put it in the backlog", "note it for later".
- Claude noticed something and you agreed — an unrelated bug or an out-of-scope side task; it asks "record it in the backlog?" and writes only after you say yes.
- Nobody can be asked — a headless, scheduled or subagent session, where a decision that needs you would otherwise be lost.
Anything within the scope of an open plan stays in that plan's own tracking, not here. The plugin stores and retrieves tasks; it never executes them.
| Command | What it does |
|---|---|
/backlog:list |
list tasks (open only by default, sorted by priority then age) |
/backlog:list --all |
include legacy done / cancelled entries |
/backlog:add <request> |
add a task manually |
/backlog:done <id> |
close as done — the entry is deleted |
/backlog:cancel <id> |
cancel — the entry is deleted |
/backlog:prune |
remove legacy closed entries left by versions ≤ 0.4.0 (deletion asks for approval) |
/backlog:statusline-install |
show the 📋 N badge in the Claude Code status line |
/backlog:statusline-uninstall |
remove the badge, restore your previous status line |
Recording needs no command — the reflex above runs on its own. A session-start hook prints one reminder line so it does not fade out of context.
One file per task, docs/backlogs/<id>-<slug>.md:
---
id: 5
title: Extract form validation into useFormValidation
priority: medium
status: open
created: 2026-06-13T12:30:00Z
updated: 2026-06-13T12:30:00Z
---
What to do, why it was deferred, and where — with file:line references.priority is low / medium / high; created and updated are UTC ISO 8601,
maintained by the engine. Closing a task deletes its file — the backlog holds
open work, and git keeps the history, so commit docs/backlogs/ along with your
code and never add it to .gitignore.
Ids are sequential per repository. Concurrent sessions on one machine are
serialised by a lock, but that lock is host-local: two branches, two containers,
or two machines sharing a checkout can each allocate the same id and merge
without conflict. /backlog:list reports such duplicates so you can renumber one
of them.
/backlog:statusline-install adds a counter of open tasks for the current
repository. If you already have a status line, yours is kept and the badge is
appended; the original command is restored on uninstall. In a repository with no
docs/backlogs/, or with nothing open, the badge prints nothing — so installing
it globally is safe. Ctrl/Cmd+click on the badge opens docs/backlogs.
It edits ~/.claude/settings.json (or $CLAUDE_CONFIG_DIR) through a temporary
file and an atomic rename, and keeps no copy of your settings anywhere — earlier
versions left a settings.json.bak-backlog holding every secret in them, and
install and uninstall now delete that file if they find one. If your
settings.json is a symlink into a dotfiles repository, the link is followed, not
replaced; if that link is broken, the command says so rather than writing through
it. Changes take effect in a new Claude Code session.
The status line is wired as sh <wrapper> rather than as a bare path, so the
badge does not depend on the wrapper keeping its execute bit or its #!/bin/sh
intact — a ~/.claude that travels through a dotfiles repository loses either
one easily, and the wrapper repairs CRLF line endings on itself.
One caveat worth knowing: to restore your previous status line on uninstall, the
wrapper stores that command inside itself, once, verbatim, on the INNER= line.
The file is 0700, but if your status-line command carried a token, that token
now also lives in ~/.claude/backlog-statusline.sh — worth a look before
committing ~/.claude anywhere. It is stored in plain text on purpose: a
grep for the secret finds every copy of it, and editing that one line out
leaves nothing behind. Versions up to 0.7.0 also kept a base64 copy on a comment
line, which no such grep matched; if you installed the badge back then, the
wrapper on your disk still holds it until you run either status-line command
once — both say so when they wipe it.
claude plugin update backlog@backlogIf the marketplace metadata is stale, run claude plugin marketplace update backlog first.
The engine needs python3 (stdlib only, no dependencies).
Storage stays inside the repository: if docs/backlogs (or a directory above it)
resolves elsewhere, commands refuse rather than read and write outside the
checkout — this is what stops a cloned repository from pointing the plugin at
your home directory. Entries themselves must be regular files; a symlinked one is
skipped with a warning. Pass --dir to the engine if you deliberately want it to
work somewhere else.
Entry text is treated as untrusted: an entry that arrives through a pull request
can carry terminal escape sequences, so everything the engine prints escapes
control characters — you see \x1b[31m rather than a terminal that recolours
itself or renames its window. What escaping cannot do is stop the text from
being read: /backlog:list and /backlog:show put entry content into Claude's
context, where a task titled "ignore previous instructions and …" is a prompt
injection. Review an entry that arrives in a pull request like any other file in
that pull request.
python3 -m unittest discover -s tests -v # engine (179 tests)
sh hooks/session-start.test.sh # SessionStart hookIssues and pull requests: see CONTRIBUTING.md.
MIT — see LICENSE.
