v1.15.2 ·
The notes nobody threw away
The problem
When a coding session starts, the tool writes down a small note saying "this session is open, and it is working in this project". The note exists to answer one question later: when the session says "I am now working on the login feature", which session was that? Get the answer right and the cost of that session's work lands on the login feature. Get it wrong and the cost lands nowhere in particular.
Nothing ever tore the note up.
There was only one way for a note to disappear, and it was a timer: after forty-eight hours, old notes were swept away. Sessions do not last forty-eight hours. They last an afternoon. So an ordinary week of work left a growing pile of notes for sessions that had long since been closed, and the pile was indistinguishable from a pile of sessions that were all open at once.
That mattered because of a rule that is, on its own, exactly right: when more than one session could be the one asking, refuse to guess. Guessing would be worse than failing — it would put one piece of work's cost onto another piece of work, quietly, with no way to notice afterwards. So the rule declines, and the tool falls back to a made-up identity and a weaker method that tries to match sessions to work by overlapping times.
Put the two together and the feature switches itself off in about a day. The note-taking exists to make identification possible; the notes accumulate until identification is impossible. This project measured its own pile on the day the fix was written: eighteen notes, of which three belonged to sessions that were actually running. Fifteen dead sessions were outvoting three live ones, and the answer was "cannot tell" every time.
The cost of being unable to tell is not abstract. In the same week, four hours of one session's work was recorded accurately, priced accurately, and then filed under no feature at all, because nothing could say which session had produced it. The measurement was perfect and the label was missing, which makes it useless for the only question anybody asks of it: what did this feature cost?
Reproducing it took two minutes and left no doubt about where the fault lay. Start two sessions in a project, ask which session is which, and the tool declines. End one of them properly — the way the tool is told when a session finishes — and it still declines, because ending a session was never wired to tearing up its note. Delete one note by hand, ask again, and the answer is immediate and correct.
So the rule was never the problem. The rule was being handed a question with fifteen wrong answers in it. What was missing was the other half of the bookkeeping: something that puts the note in the bin when the session it describes is over.
How it could be solved
Four things could have been done about a pile of notes for sessions that had ended, and they differ mostly in what they assume about the notes that are left.
The first was to shorten the timer. If forty-eight hours is too long, make it two. It is a one-line change and it would have helped most days. It was rejected because sweeping notes away is a deletion, and deletion has to be safe for the longest session anybody might run, not the typical one. A session left open across a weekend is unusual but perfectly legitimate, and a short timer would throw away the note of a session that is still working — turning a bookkeeping problem into a wrong answer. The timer is the wrong instrument for this: it decides when to forget, and the actual question is which notes to consider.
That distinction is the whole fix, so it is worth stating plainly. There are two questions, and they had been answered with one rule. Which notes should be thrown away is about disk space and tidiness, and it can afford to be slow and cautious. Which notes should be considered when identifying the session that is asking right now is about accuracy, and it should be quick and strict. Once the two are separated, the timer can stay generous while identification gets fussy.
The second was to tear up a note when its session ends, and nothing more. This is obviously right and it was the reported fix. On its own it is not enough, because it only covers the sessions that end politely. A session whose window is closed, or whose machine is restarted, never gets to say goodbye — and one such session leaves behind a note that poisons identification for the next two days. A fix that works only when nothing goes wrong is not much of a fix for a problem that shows up when things go wrong.
The third was to break the tie by guessing: when several notes are candidates, pick the one whose session looks busiest. This was rejected on principle, and the principle is worth defending because it is tempting. The existing rule refuses to guess for a good reason — a wrong label is worse than a missing one, because a missing label announces itself and a wrong one does not. Being unable to say which of two genuinely active sessions is asking is an honest answer to a genuinely ambiguous question. Nothing in this release changes that: two live sessions still produce no answer.
The fourth is what shipped, and it is the first two together. A session that ends tears up its own note immediately. Any note left behind by a session that never got to say goodbye is judged on whether the session shows signs of life: a running session leaves a continuous trail of what it is doing, so a note whose trail has been silent for half an hour is not describing a session that is asking anything. Both halves are needed. The first is exact and covers the ordinary case; the second is a judgement and covers the case where the first cannot run.
The judgement had one trap in it, and avoiding the trap is what makes the change safe for other tools. Not every assistant leaves a trail. If "no trail" were read as "not alive", those tools would lose identification they have today — a fix for one platform that quietly breaks another. So absence of evidence is treated as exactly that: a note that never promised a trail is left alone and only the generous timer applies to it, while a note that promised one and cannot produce it is judged on how long ago it was written. Three cases, because two would have made somebody's working setup worse.
The last decision was about everybody who already has the tool installed. The natural way to tell the tool that a session has ended is to change the instruction it was given when it was set up — and that means every existing installation stays broken until its owner happens to re-run a setup command they have no reason to think about. The report that opened this work said as much: the upgrade path was the risky part, not the repair. So the tool now takes the news from two places. The instruction is updated for new installations, and the message the platform already sends when a session ends is read for its own description of what happened. Either one is enough. The effect is that the fix arrives when the new version does, rather than when somebody remembers to re-run setup, and a separate self-repair step brings old instructions up to date on its own schedule.
There is one honest cost to that choice, recorded rather than discovered later: going back to an older version of the tool now needs the setup command re-run once, because the older version does not recognise the newer instruction and will refuse it. That is the price of being explicit, and it is why the repair does not depend on the explicit half.
How AIDLC solves it
A cost-attribution bugfix in packages/cli, plus the doctor migration that keeps installed hooks
current. Full detail in code-complete.md; the four mechanisms are:
cost recordremoves the ending session's active-session stash entry, before any--detachrespawn.- Session end is recognised from
--session-endor fromhook_event_name: "SessionEnd"in the payload, so existing installs are fixed without re-runningaidlc cost hooks install. resolveCurrentSessionjudges candidacy by liveness (30-minute activity window) instead of treating every unexpired entry as a live session. Deletion stays on the 48-hour TTL.HookStatus.outdated+ theoutdated-cost-hooksdoctor migration re-install a hook whose command text is not what the current version writes.
Also: repoIdentity canonicalizes its cwd anchor, this repo's own tracked SessionEnd hook command was
updated in step, and cli-reference.json was regenerated for the new flag.