Estela is a CLI that turns your local Claude Code and Codex session logs into billable hours. It does this two ways: it reads the agent's own transcripts to measure real work sessions, and it looks at your git commits to catch the work that happened without an agent open. Those two sources have to agree on who gets credit for a given stretch of time, or you end up billing the same hour twice.
Last week we shipped a fix for exactly that: a bug where deleting a file you never touch directly — Claude Code's own transcript — could quietly double your invoice.
How the two sources are supposed to not overlap
Every commit gets checked against the agent sessions that were scanned from disk. If a commit falls inside a session's time window and matches its repo, it's folded into that session — the agent already measured that time, so the commit adds nothing new. If a commit falls outside every known session, it's treated as unassisted work and billed at an estimated rate based on the diff size.
That filter only works if "the agent sessions that were scanned from disk" is actually the full picture.
The bug: disk isn't the source of truth, the database is
Claude Code prunes its own old transcript files. That's expected behavior on its end — it's not meant to keep every session forever. But Estela's overlap check was built around re-scanning whatever transcripts currently exist on disk. Once Claude Code cleaned up an old one, that session stopped existing as far as the overlap check was concerned.
The commits made during that now-deleted session didn't disappear, though. On the next import, they no longer matched any visible agent session, so they fell through to the "unassisted work" path and generated a brand new estimated-hours entry — on top of hours that had already been measured once, and in some cases already invoiced.
The fix sounds almost too simple: stop comparing against what's on disk right now, and compare against everything Estela has ever saved to its own local database instead. Every turn Estela ingests gets written to a local table and nothing ever deletes from it, even after the source transcript is gone. Once the overlap check reads from that permanent record instead of the ephemeral one, a pruned transcript can't resurrect its commits as new work.
The deeper lesson generalizes past this one bug: when you reconcile two sources of truth and one of them is a live filesystem scan, "not found in today's scan" is not the same fact as "never happened." Any logic that treats absence from a live scan as absence, period, will eventually mistake something it already knew for something new.
Two smaller bugs from the same week, same root shape
Fixing this one surfaced two related double-counting bugs that had been sitting in the same code path.
The first: the overlap check matched sessions to commits by exact repository path. A commit made at the root of a repo didn't match a session that was open in a subfolder of that same repo — so an agent session in repo/src/ui didn't cover a commit made in repo, and the commit got billed again on top of the session. The fix treats "same repository" the way the rest of the codebase already did when attaching commits to sessions: the commit's folder being the session's root or a subfolder of it counts as a match.
The second isn't a double-count, it's a different kind of integrity problem: re-running the hours calculation could silently rewrite a day the user had already approved. Approving a day is supposed to mean "this number is final" — often it's already been shared with a client. A later change to how hours are computed was overwriting approved entries along with everything else. The fix adds approved as a second guard alongside the existing already invoiced guard: once a day is approved, a recompute leaves it alone until someone explicitly un-approves it.
What we told existing users
If you'd been invoicing from Estela since this bug was introduced, the honest answer wasn't "it's fixed now, don't worry about it." It was: go check the commit-derived time entries you haven't invoiced yet, because some of them might be the ghost of a session you already billed. We said that directly in the release notes instead of papering over it.
Estela is open source. If you're tracking AI-assisted work for billing and want to see how the session/commit reconciliation works end to end, the repo is here: https://github.com/jdleonruiz/estela-cli?utm_source=devto&utm_medium=post&utm_campaign=double-billed-hours. If it's useful, a star helps more people run into this before it costs them a client's trust.
Have you hit a similar "the live scan forgot something the database still knew" bug in your own tooling? I'd like to hear how you caught it.
Top comments (0)