I lead a small engineering team on a group of e-commerce stores, and for the last few months a lot of my work runs through AI coding agents. Codex and Claude Code write code, open PRs, review each other and spin up subagents.
It feels fast. I wanted to know if it was.
What I counted
My agent tools keep everything locally: Codex in a SQLite database, Claude Code in one JSONL file per session. Add git and the GitHub CLI and you can count everything.
Over the last 60 days:
- 560 pull requests I authored were updated
- ~4,800 subagent threads in Codex, plus ~360 sessions I started by hand
- one repository had over 100 agent worktrees on disk
Then I counted what was actually finished.
About 490 branches were never merged. No PR, no review, no session of mine touching them. Most came from subagent tasks that ended with a summary and moved on.
The chat said done. Git said otherwise.
The rule: status comes from git
I built a small collector that runs every hour. It never reads what an agent said. It reads what happened:
def status(branch, prs, sessions): if any(p.state == "MERGED" for p in prs): return "done" if branch.merged_into_default and not branch.dirty and not branch.unpushed \ and last(sessions) <= branch.last_commit: return "done" if any(p.state == "OPEN" for p in prs): return "in_review" return "unfinished" # dirty, unpushed, unmerged, or a session after the last commit Work outside any repo, or a session on the main branch with no commit after it, gets unknown. I'd rather see "can't verify" than a guess.
On top of the status, a few flags:
-
waiting: an open PR nobody touched for 3+ days -
unpushed/uncommitted: work that only exists on my disk -
prod,security,deadline: keywords in titles and branches -
stale: no activity for 14 days
The noise problem
The first version listed 313 "unfinished" items. Useless.
Almost all of them were agent branches nobody would ever look at again. So branches with no PR, no session of mine and no activity for 3 days now fold into one line per repo: "12 unmerged branches with no PR". Cleanup, not work.
The other surprise was speed. git status on 100+ worktrees on an external disk took minutes. The collector now only runs it on checkouts a session actually used in the last two weeks. A full run takes about 25 seconds.
No AI inside
The collector is plain Python and the standard library. No model decides anything. That's the point: if the tracker can hallucinate, I'm back to trusting the chat.
The output is a few Markdown files and one JSON file in a private repo. A chat assistant reads those to plan my day, but it can't change the status. Only git can.
What the first run found
- Pull requests waiting six days for a review
- Uncommitted files sitting on a default branch
- A closed PR whose branch still had commits nobody pushed
None of it was hidden. I just had no single place that told the truth.
What I took from it
Agents make starting work almost free. That's the trap: you start more than you finish, and every thread ends with a confident summary.
Finishing is still a human job. Now at least I can see which ones.


