claude-todo-mirror
A Claude Code plugin that mirrors TodoWrite state to per-session markdown files
with hierarchical checkboxes — keep an always-visible task view open in VS Code,
Obsidian, or any markdown viewer instead of scrolling back through chat history.
The problem
Claude Code's inline TodoWrite updates scroll out of view as the conversation
grows. When you want to know what's left, you scroll back through messages.
The built-in Tasks panel helps, but it's tied to one window and doesn't
expose a hierarchy or an external file you can pin in another editor.
What this plugin does
On every TodoWrite call, a hook writes a markdown checklist for the current
session to <project>/.claude/todos/session-<id>.md and refreshes
_index.md with all sessions in this project. Open either file in VS Code,
Obsidian, or tail -f it from a terminal — it auto-updates.
Sample output
# Session `abc12345-...`
**Project**: `/Users/me/code/my-app`
**Updated**: 2026-05-07 17:40:21 KST
**Progress**: 2/7 (29%)
**Now**: ▶ Verifying render_todos.py
---
- [x] Plugin scaffolding
- [x] Hook script
- [▶] Verifying render_todos.py
- [ ] edge case: indented children
- [ ] edge case: empty todos
- [ ] README + LICENSE
- [ ] GitHub push
Hierarchy convention
TodoWrite items are flat by spec, so this plugin uses leading whitespace in
the content field as the hierarchy signal. Two spaces (or one tab) = one
indent level:
TodoWrite([
{"content": "Parent task", "status": "in_progress", "activeForm": "Working"},
{"content": " Child task A", "status": "pending", "activeForm": "..."},
{"content": " Child task B", "status": "pending", "activeForm": "..."},
{"content": "Sibling task", "status": "pending", "activeForm": "..."},
])
Tell Claude in your project's CLAUDE.md (or per-prompt) to follow that
convention when it writes nested todos.
Status mapping
| TodoWrite status | Rendered |
|---|
pending | [ ] |
in_progress | [▶] |
completed | [x] |
The first in_progress item is also pulled into a Now: ▶ ... header line so
you can see the active task at a glance.
Per-session isolation
Each Claude Code session gets its own session-<id>.md. Run multiple sessions
in parallel — the plugin keeps them separate. _index.md summarizes all of
them in one table sorted by last activity:
| Session | Progress | Now | File |
| --- | --- | --- | --- |
| `abc12345` | 2/7 (29%) | Verifying render_todos.py | [session-abc...md](./...) |
| `def67890` | 4/4 (100%) | - | [session-def...md](./...) |
Install
This repo is a single-plugin marketplace — marketplace.json is committed
at .claude-plugin/marketplace.json so you can install it via the standard
plugin commands.
Heads up: /plugin ... commands only work in the Claude Code CLI
(terminal), not in the Desktop app. Once installed, slash commands like
/todos-watch work in both Desktop and CLI.
Open a Claude Code CLI session and enter these two commands separately
(do not paste them on the same line — Claude Code parses the second one as
part of the first command's URL):
/plugin marketplace add bighaeil/claude-todo-mirror
Then, on its own:
/plugin install claude-todo-mirror@claude-todo-mirror
When the install dialog asks for scope, "Install for you (user scope)" is
the right choice for personal use — the plugin becomes available across every
project and any Claude Code surface (CLI, Desktop).
For local development without going through the marketplace:
claude --plugin-dir /path/to/claude-todo-mirror
After install, every TodoWrite call in any project will create
.claude/todos/ under that project's root.
Manual hook registration (without /plugin install)
If you can't or don't want to use the plugin marketplace, drop this into your
project's .claude/settings.local.json (or user ~/.claude/settings.json):
{
"hooks": {
"PostToolUse": [
{
"matcher": "TodoWrite",
"hooks": [
{
"type": "command",
"command": "python3 /absolute/path/to/claude-todo-mirror/scripts/render_todos.py"
}
]
}
]
}
}
Recommended workflow
- Open
<project>/.claude/todos/_index.md in VS Code (or Obsidian) and pin
the tab.
- Or open the active session file
session-<your-session>.md directly.
- Work as usual — the file refreshes on every
TodoWrite call.
For multi-session overview, the _index.md is the single source of truth.
Live terminal monitor (/todos-watch)