v1.12.0 ·
The fix that deleted its own alarm
Two days after v1.10.8 fixed cost capture going dark, it went dark again for forty-one hours. The command whose whole job is to say whether capture is armed said installed the entire time.
The v1.10.8 fix replaced frozen absolute paths with a small generated script that resolves node and the CLI at run time and logs when it cannot. That script is generated per install and deliberately not tracked, because it embeds hints specific to one machine. Committing it would make one developer's answer everybody's. But the settings file that arms the hooks is tracked. So the instruction to run the script survives a clone, and the script does not. Every fresh checkout started armed against a file the repository could not supply, and said nothing.
The sharper half is why nothing caught it. That earlier fix had also deleted the check that asked whether the thing a hook points at still exists. The reasoning sounded right: the hook no longer names a path, so no path can go stale. The premise was true and the conclusion did not follow. The path could not go stale, but the file it resolved to could be absent. I recorded the failure class as retired, not just detected, and it came back through the gap the retirement left.
A second failure underneath
The command that reconciles historical usage had recovered nothing for a week: forty-two of forty-two sessions skipped, zero attributed. It joined on session id. When the hook cannot run, the session-start half never records the real platform id, so a claim falls back to a synthetic one. The id on the claim and the id on the usage record are two names for the same work, and no equality join can match them.
What changed
The status command now reports a third state, armed but not working. It names what is wrong (missing, not a regular file, not executable) and the command that repairs it. Three readers had been saying the reassuring thing and now say the accurate one.
A doctor migration regenerates the script and applies without asking, because the file is generated and holds nothing anyone wrote. I had tried report-only for this failure class once already. The detection was correct, the message printed on every update, and the data was lost anyway. A repair that needs to be noticed does not happen.
I considered tracking the script, which brings back the failure v1.10.8 removed, and going back to absolute paths, which undoes that whole fix to solve a smaller problem. Keeping the script and checking it is there was cheap. The check has to live outside the hook, because a hook that cannot start cannot report that.
For attribution, the identifiers disagree but the clocks do not. A claim knows when it was held, and a usage record knows the window it covers. Reconciliation now has two passes. One walks the local usage records directly, so usage from a session that never claimed becomes reachable. The other moves records out of the unattributed ledger into the instance whose claim window covers them, deciding on time. Here it recovered 811 records and captured about 4,400 that had never been recorded, taking instances with no cost data from ten down to three.
Three traps turned up, and each has a test now. An unattributed record's session id can be a provenance stamp, not an identity. Reading it as identity found exactly one matching claim window and restated one instance eightfold, until two existing tests caught it. A duplicate guard keyed on session id, the field that disagrees, did nothing, passed the whole suite and produced several thousand phantom records. I found that in a diff of the dollar totals. And the transcript directory holds every project on the machine, twelve here, so a time join across all of it would have charged this project for other work. Scoping now follows each record's own declaration of where it ran. A total that is too low looks incomplete. One that is too high looks true.
What it does not fix
Attribution repairs itself for one session per checkout. Two concurrent sessions in one checkout still fall back to synthetic ids. That leaves 1,077 records unattributed for that reason, and 2,969 more because no instance was claimed when the work happened. Both are now reported out loud instead of rendering as zero.
This post is hand-written because there is no instance behind it. I did the work straight through, to stop circling the problem, so there is no ideation, requirements or design record to generate from. That is the same shape as the defect. A release meant to make the project's own measurement honest is attributed to no instance, and adds to the 2,969.