A customer tells you their October usage invoice is short by one day. You open the code that builds the billing window and it looks deliberate: an exclusive end boundary, a comment saying the caller already normalises the date to UTC. You did not write that. An assistant did, eleven days and about forty commits ago, in a session you reviewed and approved. To trace an AI regression like this one, finding the diff is the easy half. The hard half is that the code reads as intentional, because at the moment it was written, it was.
This is a different problem from catching a bad change before it lands. Reviewing what your assistant changed works when the context is still in your head and the diff is still on screen. Days later, both are gone, and what you need is a way back to the reasoning.
I'll use that billing window as a running example below. It is hypothetical, but the shape is common: a small boundary change inside a session that was mostly about something else.
Why "which commit broke this" isn't enough to trace an AI regression
Bisecting assumes you can tell good from bad with a command. For a crash or a failing test, that holds, and git bisect with a script is still the fastest thing you can do. The cases that bite are the ones where the symptom is "this number is wrong for some customers in some months". You cannot automate that check without first understanding the rule, and understanding the rule is the thing you lost.
So people reach for a git bisect alternative and usually land on git log -p over the file, scrolling until something looks suspicious. That does find the commit. It just answers the wrong question. The commit tells you the end boundary moved from inclusive to exclusive. It does not tell you whether that was the point of the change or a side effect of something else, and when the commit message was drafted from the diff (your own or the assistant's), it restates the change in prose instead of explaining it.
Code written with an assistant is especially unhelpful here, because it is internally consistent. There is a comment. There is a guard clause. The variable names match the new assumption. Nothing looks like an accident. When you debug AI generated code months later, the tell you rely on with human code, that a rushed fix looks rushed, is missing.
Walking back through sessions instead of just diffs
If you kept the sessions, you have something better than a diff: the instruction that produced it.
The walk-back is narrower than it sounds. You are not rereading transcripts. You are doing a lookup in three steps.
- Get a rough window from the code.
git log --follow -- src/billing/window.tsgives you dates, even if the messages are useless. Say the suspect commit is from 18 September. - List the sessions in that window and pick the ones that touched this area. Flat Markdown files with dated, descriptive names make this a matter of reading a directory:
.prjcontext/sessions/
2026-09-15-rate-limit-middleware.md
2026-09-18-timezone-handling-billing.md
2026-09-20-invoice-pdf-layout.md
- Read your side of the conversation first, not the assistant's. The cause is usually in what you asked for.
In the example, the 18 September session was about a genuine bug: two different parts of the app disagreed about when a day ends for users in Auckland. Somewhere in that conversation you said to make the boundaries consistent and to prefer half-open ranges. That is a reasonable instruction. The assistant applied it to the billing window too, mentioned that it had, and you skimmed past it because the Auckland bug was fixed and the tests were green.
That is the root cause, and no diff contains it. The diff contains the consequence. Finding the root cause of a regression here means finding the instruction, and then deciding whether the instruction was wrong or just over-applied. Those lead to different fixes: narrow the rule, or keep it and correct the one place that needed an inclusive end.
This is also the argument for being selective about what you keep. A session where you worked out a rule that now governs several files is worth saving; the one where you renamed variables is not. Which sessions are worth keeping goes through the filter in more detail.
What a decision record tells you that git log can't
Check decisions before transcripts. A good record answers in four lines what a session answers in four hundred:
### 2026-09-18 — Half-open date ranges everywhere
Decision: All internal date ranges are [start, end), UTC.
Why: Auckland users saw duplicate rows at day boundaries.
Consequence: Billing windows must add one day to the user-visible end date.
Status: Active
With that in front of you, the bug is diagnosed in a minute. The decision was deliberate, the consequence was known, and the billing window never got the compensating change. Without the record, you spend an hour deciding whether the exclusive end was a mistake at all, and there is a real chance you "fix" it by reverting the boundary and reopening the Auckland duplicate.
The pattern worth noticing: records that state a consequence are the ones that pay off during debugging. "Chose X over Y" is fine for history. "Chose X, which means every caller now has to do Z" is a checklist for the places you forgot. Lightweight decision records covers the format; project memory covers where these live alongside the rest.
Close the loop while you still remember
When the diagnosis is fresh, write three things down before you move on. Amend the original decision with the consequence you just discovered. Add a note about the asymmetry you now know exists (user-visible end dates are inclusive, internal ranges are not) so the next session does not re-flatten it. And keep the debugging session itself, because the next person to hit a date boundary question in this project will want it.
None of this needs tooling beyond files in a folder and the discipline to use them. What tooling helps with is making sure those files are on the machine where the bug surfaced, at the version the bug was introduced. We built PrjLab for that: prj push stores instructions, memory and sessions beside the code as an immutable version, so you can open the sessions from 18 September in the browser, diff that version against the one before it, and read the reasoning and the change in the same place. If you want to try it on a project you are already debugging, the getting-started guide walks through the first push.