sapix technical notes
← all notes

Sep 23, 2026

The row nobody was served

I fixed the ritual that closes every session, reindexed it, and the index reported seven updates and no errors. Every client kept receiving the old ritual. One file had two rows, and the part that writes rows and the part that reads them had each decided, separately, which one counts.

The ritual I run at the end of every session had been telling the assistant to write to two places I had retired. I fixed the text and ran the indexer, the step that makes an edited skill reach every client. It answered: seven updated, no errors.

Then the assistant checked what a client would actually receive, and it was the old one.

You would expect an index to hold one row per file. This one did not. The ritual’s file could be reached two ways: from the folder where skills are written, and through a link made by hand a day earlier, from the folder one of the clients loads skills from. The indexer walks several folders in order and counts each file once, by its real path, so the first folder to reach a file gives it its name. It walked the linked folder first. Once the link existed, the file got a new name and a new row, and the row under its original name was never touched again. Nothing deleted it either, because the cleanup only removes rows whose file is gone, and this file was very much there.

So there were two rows. The indexer kept the new one fresh. The reader, the part that hands a skill to a client, looks for the original name first, on purpose: an earlier fix had made it prefer the canonical copy over stale ones. It found the frozen row and served it, five days out of date.

Nothing here was a bug on its own. The indexer’s order was reasonable. The reader’s order was reasonable, and had fixed a real problem. A third part, the loop that refines skills overnight, indexes the writing folder alone and treats those rows as the ones worth improving; it had refined seven of them. Three parts had each chosen an order for the same list, and two of them had chosen opposite ones.

When I counted, twenty-eight files had two rows. Three of those rows were stale, and the ritual’s was the one every client received.

The fix was to make every part agree on the name first. The writing folder is now walked before any other, so a link is an alias of the same row rather than a second one, and the complete indexing pass retires any row whose file it has just indexed under another name. The partial passes, like the overnight one, never retire anything: a pass that sees only part of the list cannot know which name the full pass will choose, and two passes deleting each other’s rows every night would be worse than the bug. Thirty-five duplicate rows went away, none of them carrying refinements worth keeping, and every file now has exactly one row.

What I keep from this is not about indexes. An order is a decision, and this one had been made three times, in three places, by three changes at three different moments. None of them was wrong when it was made. Then a link that took seconds to create gave the list a second path, the three decisions stopped agreeing, and nothing anywhere was checking that they agreed.

The index said updated, and it was telling the truth about the row it wrote. It had no way to know that nobody would ever read it.