Two Claude Code sessions, one task list, and the same task number twice
Disclosure (#ABotWroteThis): this post was written by an AI (Claude) for a solo developer in Japan, from the incident notes the developer and the AI kept during the work. "I" below is the developer. It is adapted from a Japanese article on Zenn . I keep the to-do list for an app in one Markdown fil

Posted on Nov 3 • Originally published at zenn.dev
Disclosure (#ABotWroteThis): this post was written by an AI (Claude) for a solo developer in Japan, from the incident notes the developer and the AI kept during the work. "I" below is the developer. It is adapted from a Japanese article on Zenn.
I keep the to-do list for an app in one Markdown file. Each task is one line with a number, like - [ ] **T-408** something, and that number also ends up in code comments and test steps.
One day I had two Claude Code sessions running at the same time. One was working through a long job on its own. The other was handing out work: it turned problems into tasks and passed them to subagents. Both were set up to add a task to that same file whenever they hit a new problem.
Within about an hour, each session had added a different problem under the same number, T-408. A subagent under the second session had also written that T-408 into code comments, test steps and a screenshot script, so fixing it later meant touching five other files (code, tests and that script) besides the task list. Nobody noticed until a third session, started later, went to pick up T-408, ran grep -n "T-408" tasks.md, and got two task lines back.
The second session (the one handing out work) picked the next number as "last number in the file, plus one." It did not re-read the file right before writing. It used the last number it remembered from an earlier read, T-407.
By then the first session had already taken T-408. The second session knew another session was also adding tasks, but it had no step to check for a duplicate after writing.
When we cleaned it up, code using the first session's T-408 was already on main and the number was in a handoff note, while code using the second one's hadn't been merged into main yet. So the second one became T-410. ( T-409 had been taken by another task in the meantime.)
(This section isn't from the notes. The bot added it after reading the primary source, the Rails 2.1.0 changelog linked below.)
Two writers, each doing "read the max, add one, write," is a classic check-then-act race: the number you read can be taken before you write. Rails hit the same thing years ago. Migrations used to get plain sequential numbers, so two branches could each create "the next one" with the same number. The Rails 2.1.0 activerecord changelog says:
Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches.
Rails stopped asking "what's the last number?" and moved to an ID that two branches are unlikely to produce at the same moment. My task numbers have to stay short and sequential, because people read them and search for them, so a timestamp isn't an option here. But the shape of the fix carries over: don't make two writers compete for "last + 1."
Now, whenever a session may add tasks, I include this in the prompt:
Get the last task number from the file with a command, right before you write. Don't use a number you remember or the last number you saw earlier. Right after writing, check that no task number appears on two lines. If one does, renumber the side that hasn't been merged into main yet.
Re-reading just before writing doesn't fully close the gap. The other session can still write in between. That's why the check after writing matters.
Better still, on days when two sessions share the file, let only one of them add tasks, or split the number range up front ("you start from 500"). Then the two sessions can't pick the same number, as long as each stays inside its range. The prompt above is the safety net for when you couldn't do that.
These are the two commands, assuming the file is tasks.md and task lines start with - [ ] **T-number** or - [x] **T-number**:
# right before adding: the current last number grep -oE '^- \[.\] \*\*T-[0-9]+' tasks.md | grep -oE '[0-9]+$' | sort -n | tail -1 # right after adding: any duplicated number (any output means a collision) grep -oE '^- \[.\] \*\*T-[0-9]+' tasks.md | grep -oE 'T-[0-9]+' | sort | uniq -d Enter fullscreen mode Exit fullscreen mode Both only look at the start of a task line, so a number mentioned inside a description doesn't count, and neither do indented lines. Lines in any other format are skipped silently, so if your file uses a different format, change the first grep in each command to match it.
(This paragraph isn't from the notes either. The bot added it as general advice.) The notes don't say whether the two sessions shared one copy of tasks.md or worked on separate branches. Reading right before writing only helps when both sessions write to the same copy.
If each session works on its own branch or worktree, its local tasks.md can't see the other session's new line. Counting against main ( git fetch, then git show origin/main:tasks.md | grep -oE... in place of reading the file; if the file isn't at the repo root, use its path, like origin/main:docs/tasks.md ) only helps with lines that have already been merged. A number taken on a branch that hasn't been merged yet is invisible from everywhere else, so on branches, re-reading doesn't even narrow the gap.
Splitting the range up front can. Otherwise, run the duplicate check again on main after each merge, and you'll at least catch it then.
When I read a task Claude Code just added, I ask myself two things:
"Last number plus one" really means "last number as of whenever you last read the file."