Have you ever noticed that Claude Code's TodoList looks well-organized, yet the underlying work still feels sloppy? I went through the same phase. Early on, I was fixated on filling the TodoList with neat bullets, and in the process the actual craftsmanship behind each item kept slipping.
What I eventually realized is that what matters is not whether you write a TodoList, but how granular each item is, when you update them, and whether you build verification into the list itself. Drawing from the automation I run across my four sites, here are three patterns that have made a real difference.
Why "I wrote a TodoList" is not the same as "I'll do quality work"
The TodoList is undeniably useful. It breaks down complex tasks, gives you visible progress, and reassures both humans and agents. But there is a subtle trap.
A TodoList is a tool for visualizing a plan, not a tool for guaranteeing execution quality. When items are too coarse, the details of each item stay ambiguous, and the work ends up being cut-to-fit on whatever feels convenient. Consider this list:
- [ ] Refactor the component
- [ ] Write tests
- [ ] Update documentation
It looks tidy, but the moment the agent starts, "which file" and "how" are left to improvisation. You end up with a change set that fixes the files that happened to be open, then calls it done.
Pattern 1: Slice tasks into "verifiable units"
The biggest win for me was treating each TodoList item as something whose completion you can verify objectively. The item itself should make it unambiguous what "done" looks like.
Compare bad and good examples:
❌ Unverifiable
- [ ] Improve API response caching
- [ ] Harden error handling
✅ Verifiable
- [ ] Implement a Map-based cache with a 5-minute TTL in src/lib/api-cache.ts
- [ ] In src/app/api/checkout/route.ts, branch on Stripe's StripeCardError in the try/catch and return a user-facing message
- [ ] Add Vitest tests in tests/api-cache.test.ts covering cache hit, miss, and expiration
In the good example, each item carries its own acceptance criteria. Does the cache use a Map? Is the TTL five minutes? Does the handler branch on StripeCardError? You can answer those by reading the diff.
A simple rule of thumb I use: write items as "verb + target file + completion criterion." When all three are present, the granularity tends to land in the right zone.
Pattern 2: Always end with a verification step
The next pattern is to close every TodoList with an explicit verification step. Without this, agents treat "all items checked = done," and subtle breakage slips through.
My template looks like this:
- [ ] (work item 1)
- [ ] (work item 2)
- [ ] (work item 3)
- [ ] [VERIFY] tsc --noEmit passes on all touched files
- [ ] [VERIFY] vitest run stays green on the existing suite
- [ ] [VERIFY] Manually open /, /en/articles/... and two related pages to confirm nothing is visually broken
Adding a verification step visibly lifts final quality. The reasoning is simple: the agent knows it cannot close the list until verification is also done, which kills the urge to cut corners at the finish line.
For higher-stakes work, I delegate the verification to a separate subagent via Claude Code's Task tool. A fresh agent checking the work removes the author bias that creeps in when the same agent verifies itself.
Pattern 3: Pin update timing to "one item at a time, in real time"
The third pattern is about update timing. The official docs recommend it, but whether you actually enforce it in practice changes everything.
The rule is simple: flip an item to in_progress immediately before you start it, and flip it to completed immediately after you finish it. No batching.
Why batching updates hurts you
I once tried "do five items, then mark them all completed at the end." It felt efficient, but in practice:
- When an error surfaced mid-batch, I had no idea which items had genuinely finished
- If the session disconnected, the saved state no longer reflected reality
- Verification had to cover all five items, making triage slow
Switching to one-at-a-time updates made mid-task recovery radically easier. Claude Code can pick up a previous session's TodoList, so as long as the state is accurate it can resume naturally from the right spot.
Implementation: a TodoWrite-aware prompt
Here is the skeleton of a prompt I use for article automation. Treat it as a typical shape for a TodoWrite-driven workflow:
# Task: write one article and push it
Before starting, register the following TodoList via TodoWrite.
1. [pending] Pick today's topic from GSC data and confirm the slug is not duplicated
2. [pending] Create the Japanese MDX in content/articles/ja/claude-code/ (>=3,000 characters, consistent polite voice)
3. [pending] Create the English MDX in content/articles/en/claude-code/ (>=1,500 words)
4. [pending] [VERIFY] Confirm find content/articles/ja -name "*.mdx" | wc -l matches the English count
5. [pending] [VERIFY] Grep the new MDX for internal links and confirm every target article exists
6. [pending] Run git add, commit, and push
7. [pending] Append an entry to _updated_article_log/
Update each item one at a time: flip to in_progress when you start, completed when you finish.
If a verification step surfaces an issue, add a new pending item before fixing it.The key is that verification is an explicit item, and the branch for "what to do when something fails" is also governed by the TodoList. That habit alone reduces the number of issues that slip through.
When not to use TodoList
Finally, a few situations where TodoList is not the right tool. It is not a universal hammer.
- Conversational Q&A: "What does this function do?" or "Why is this erroring?" does not need a TodoList. It only increases cognitive load and buries the actual answer.
- Tiny single-file edits: For a typo fix or a few-line change, creating a TodoList costs more than it saves. Go straight to Edit.
- Exploration: While you are still figuring out how to implement something, the investigation is fluid by nature. Freezing it into a TodoList kills flexibility. Explore first, decide the direction, then TodoList it.
Personally, the sweet spot is tasks that will take at least 15 minutes and span multiple files or multiple verification points. That is where the TodoList truly earns its keep.
Going deeper
If you want to see how TodoList fits into the broader picture of agent design, pair this article with Using Claude Code's Plan Mode to isolate the design phase and Solving Claude Code's long-running task failures through operational design. Together they cover planning, execution, and recovery.
Your next step
Pick one task in your day that will take more than 15 minutes, and turn only that one into a TodoList. Add at least one explicit verification item at the end. That small commitment alone will change how the final output feels. Once you are comfortable, graduate to delegating verification to a separate subagent — that is where truly stable automation starts.
A concrete walkthrough: a two-hour refactor with TodoList
To make these patterns tangible, here is a condensed version of an actual session I ran last week — a two-hour refactor where I had to move a payment flow from a single Next.js route into a set of shared helpers.
The initial TodoList looked like this:
1. [pending] Extract the Stripe session creation into src/lib/stripe/checkout.ts with named exports
2. [pending] Extract the webhook signature verification into src/lib/stripe/webhook.ts
3. [pending] Update src/app/api/checkout/route.ts to import the new helpers
4. [pending] Update src/app/api/webhook/route.ts to import the new helpers
5. [pending] Add unit tests in tests/stripe/ for both helpers (minimum: happy path + one failure case each)
6. [pending] [VERIFY] tsc --noEmit passes across the project
7. [pending] [VERIFY] vitest run is green
8. [pending] [VERIFY] Manually walk through a test checkout on localhost to confirm end-to-end behavior
Halfway through item 3, I hit a subtle issue: the old route was logging metadata in a format the new helper did not yet preserve. Rather than quietly patching it, I added a new pending item before continuing:
3.5 [pending] Preserve the existing checkout-metadata logging format in the new helper
That one-line discipline — never make an unplanned fix without first adding it to the list — is what keeps long sessions honest. When I reviewed the commit later, every change had a traceable origin.
The mental shift: from "listing" to "contracting"
Looking back, the biggest change in how I think about TodoList is this: I used to treat it as a list of things to do. Now I treat it as a contract I am handing to the agent, including the acceptance criteria.
When each item reads like a small contract — "complete X in file Y, verified by Z" — you stop needing to supervise the agent line by line. You can step away, let it work, and audit the outcome against the contract when you come back. That is what unlocks the real productivity gain of Claude Code.
The patterns in this article are not about making the list longer. They are about making each item trustworthy. Once that click happens, a five-line TodoList can carry a two-hour task reliably, and you spend your attention where it actually matters.