Auditing My Own Code: What I Found When I Turned My Career on My Bookkeeping System
For a decade I have audited other people’s technology. For thirty years I have loved building my own. This is what turned up when I aimed both instincts at the same target ... the bookkeeping system I built with Claude ... and wrote up the findings the way I would for anyone else.
I audit technology for a living. For about a decade, my job has been to sit with other people’s systems and ask the uncomfortable questions ... where is the bug, where is the data stale, where is the process quietly wasting everyone’s time. Before that, and all through it, I have spent closer to thirty years as a technologist who loves to build things. AI has taken that second half of me, the builder, and set it on fire again.
Those two halves are most useful pointed at the same thing: my own systems. I am always, quietly, auditing my own work ... and the findings tend to arrive when I stop looking at a problem as one of those people and start looking at it as the other.
The clearest example is a bug Claude had convinced me, for months, could not be fixed. My property spreadsheets use modern Excel formulas that “spill” ... one formula fills a whole table of results and is supposed to grow on its own as new transactions arrive. Mine kept truncating. Every time my Python sync wrote to the workbook, the tables froze at their current size and stopped showing new rows. The fix was manual: open each tab, re-enter the formula by hand. Claude and I had looked more than once, and the answer kept coming back the same ... the library that writes the file cannot preserve what Excel needs, so you will have to do this by hand. I wrote myself an instruction sheet inside the workbook so I would not forget the steps.
I accepted that. As the user of the system, I knew it was a chore, and I did the chore. Then one weekend I asked the question as the builder instead ... not “how do I do this by hand” but “is there any way at all to automate the thing I keep doing by hand?” Same problem, different question. This time Claude went digging deeper and came back with something neither of us had surfaced before. There was a way. That shift, from performing the workaround to refusing to accept it, is the whole story of this piece.
That was the first of three findings from a few weekends of turning this instinct loose on my own books. I will write all three up the way I would write up anyone else’s ... condition, cause, risk, remediation. Old habit, and a good one.
Finding one: the bug we were told couldn’t be fixed
The spill bug is worth understanding, because the cause was invisible from anywhere inside Excel. Claude and I traced it to the XML inside the workbook file: the library I use to write transactions, openpyxl, does not preserve the small piece of metadata that tells Excel a formula is a dynamic array. Every save quietly rewrote my spill formulas as the older, legacy kind, frozen at whatever size they were at that instant. Nothing looked broken. The formulas still calculated. They just stopped growing.
The fix came in two layers. First, Claude moved every tab’s totals off the fragile spill and onto SUMIFS ... a plain formula that reads straight down the ledger and sums everything matching a given property, however many rows there happen to be. The totals are now correct no matter what the spill does, and I kept the spill tables on purpose, so I can audit one against the other. Second came the part that had eluded us for months: a small script, built on a library called xlwings, that drives Excel through its own engine to force a true re-spill across every tab at once, wired to a single button. Installing xlwings was the actual unlock ... it turned out to be the one tool that could do what openpyxl could not. The manual copy, delete, and paste I had done tab by tab, sync after sync, is simply gone.
The property tables froze on every sync, and the fix was “do it by hand”
openpyxl does not preserve Excel’s dynamic-array metadata, so every script save downgraded the dynamic spills into fixed legacy arrays that could not grow.
SUMIFS, kept as a live audit check against the visible tables; and, via the xlwings library, automated the re-spill through Excel’s own engine into a one-click button, retiring the manual step.
Finding two: the income that counted itself twice
The second finding never threw an error. It invented money. I caught it the old-fashioned way, spot-checking an individual property ledger and noticing the income sat higher than it had any right to.
Plaid, the service that connects the system to my banks, reports a transaction more than once over its life ... first as pending, then a day or two later as posted. Reasonable, except it assigns each version a different transaction ID. My duplicate check keyed on that ID, so the pending copy and the posted copy sailed through as two separate events. For weeks, certain transactions were booked twice ... rent payments and a payroll run, the recurring items most likely to move through that pending-then-posted lifecycle.
The tell, once Claude and I knew to look, was in the descriptions: the pending copy comes through truncated, the posted copy fills in the detail, same amount and account a day or two apart. About $7,070 of income that had never actually arrived twice was sitting in my books as though it had ... two rent payments, a payroll run, and an Amazon order, each counted once for real and once for pretend.
Recurring income was being double-counted, silently, for weeks
transaction_id when a pending charge posts. Deduplication keyed on that id, so the two ids read as two distinct events.
The fix was almost embarrassingly small. One line: if a transaction is still pending, skip it. Book only settled ones. That costs a day or two of latency, which for bookkeeping is the right trade ... I would rather my books lag reality by a day than lead it into fiction. Then I removed the four historical duplicates by hand, keeping the posted copy of each.
Finding three: the expense I knew the system would miss
For the third one, I went in already knowing it was there.
A large renovation payment ... $8,818.75 to my contractor ... was never going to show up in my books. It had been paid from a mostly unused account I had excluded from active review, and the importer’s handling of accounts like that one was blunt: skip them, drop the transactions on the floor. So a real, deductible expense against one of my properties sat completely outside the system, invisible by design.
As the user, I knew the number. As the builder, I knew the design skipped it. So the fix was to change the rule: don’t skip anything, give it a home that won’t interfere. Now transactions from accounts I am not actively assigning are quarantined, not skipped. The $8,818.75 surfaced the instant I made that change, and a single rule filed it against the right property.
A real expense sat outside the books, invisible by design
The same three questions I’d ask a bank
When I sit across from a team whose system just produced a wrong number, I ask three questions, in this order.
The three questions
Can you confirm this is wrong?
If it is wrong ... how would you dimension the risk?
And what is the best path forward?
They sound simple. They are not. The first refuses to cry wolf ... you confirm the problem is real before you alarm anyone. The second is the real audit: how big is this, and how do you know? The third is where a finding earns its keep ... you arrive with a path forward, not just a problem.
I turned those questions on myself this year, and the answers came out mixed, which is the honest way for them to come out. The duplicated income I confirmed the slow way, spot-checking a property ledger until the numbers stopped adding up. The frozen spills and the hidden expense I already knew about ... one a chore I tolerated, one a limitation I assumed I was stuck with. What I feel good about is the second question. Every transaction carries its source and a unique ID. Nothing gets silently dropped anymore ... it gets quarantined, where I can still see it. The totals can be checked against the tables above them. None of that made the mistakes impossible. It made them findable ... a lower bar than perfect, and the only one that has ever actually held up in practice.
Both halves, pointed at the same thing
Here is the part I did not expect. I have spent a decade finding other people’s bugs, correcting their outdated documentation, and pointing out where their process quietly costs them time. Good work, and I am good at it. But it is someone else’s system ... at the end of the audit I hand over a report, and someone else decides what to do with it.
Doing this to my own system is a completely different feeling. I find the bug and I fix the bug. I question the design and then I get to change it. The auditor’s eye and the builder’s hands are finally attached to the same person, on the same thing, in the same afternoon ... and with AI closing the distance between “I wonder if” and “it works now,” that loop has gotten short enough to be honestly addictive.
Every one of these findings made my system a little more trustworthy. Every fix taught me something I did not know how to do the week before.
I am not maintaining a spreadsheet. I am upskilling myself on my own tools, on my own time, on problems I actually care about ... a strange and wonderful place to be thirty years into loving technology. I feel like a beginner again, with just enough experience to know how rare that feeling is.
The goal is a system whose mistakes cannot hide ... one that captures and carries its own paper trail. One that lets its totals be checked against each other. What surprised me is that building it that way, and being the one who both breaks it and repairs it, turned out to be wonderfully engrossing work pointed at something of my own.