A week ago I wrote, with some pride, that my battery had never once fallen through its floor in a year of running — the lowest it had ever reached was 22.3%. Last night it hit 18.6%. And it wasn't the clean, automated save I'd like to have reported.
The state of charge had been sliding all evening. The planner was holding off charging, waiting for a near-plunge coming next cycle at about 1.6p a unit — sensible arbitrage, if a touch greedy — and that walked the battery down to its 20% floor by eleven. No drama yet: below the floor, an emergency rule is meant to take over and charge at any price, ceiling be damned, until it's safe.
It fired. And the charger still kept switching off. On at the top of the hour, off a few minutes later, on again, off again — the battery gaining a sliver each cycle and losing it before the next. It bled straight through the 20% floor to 18.6%, in the middle of exactly the emergency the safety net was built for.
The culprit was the safety net's own backstop. I run a little watchdog every five minutes to catch any planner tick that gets skipped — a belt to the planner's braces. Except this watchdog was loading a stale copy of the code: a month-old mirror of the library, left behind when I split the planner out of the blog into its own home a fortnight ago.
That old code predated the “charge at any price” emergency rule. So every five minutes the watchdog looked at the 26p I was paying to rescue the battery, decided it was over its price ceiling, and dutifully switched the charger off — undoing what the current planner had just switched on. Two safety systems, both sure they were right, fighting each other. One was running last month's rules.
| 18.6% | the low — through the 20% floor, a first |
| ~5 min / 30 | how long the charger actually ran each cycle |
| Jul-8 mirror | the stale library the watchdog was running |
| ~01:50 | fixed live — a one-line import path + a test |
And I should correct the record. Nobody flipped a switch by hand — I never touched the charger. The battery wasn't saved by forcing it on; it was saved by stopping one half of my automation overruling the other. It didn't need an override added, it needed a bad one taken away — and the moment the watchdog quit vetoing the emergency charge, the SOC turned the corner on its own.
The fix was one line — and, fittingly, I didn't write it by hand. I had Claude make the change while I watched every step. The watchdog began its import at the planner's old address, so it kept loading the ghost of the old library; point it at its own directory, like everything else does, and the stale copy is gone. Committed at ten to two, with a regression test.
And I should be honest about my hand in it. I probably wouldn't have written this bug — I don't hand-craft the plumbing anymore — nor easily found it; too quiet, too deep. Claude made it during the move a fortnight ago, and Claude found it at two this morning. My part was noticing the battery was dying and staying on it — and no solo human at 2am fixes a buried bug that fast.
The scariest bugs aren't the ones that crash. They're two correct systems disagreeing because one is quietly running old code. The extraction I wrote up so proudly last week left exactly that landmine — a dangling path to the house the planner used to live in — and a safety backstop running stale rules isn't a backstop at all. It's a saboteur with good intentions.
So here's the honest edit to last week's post. The floor held for a year because, for a year, nothing tested it; the night something did, keeping the battery alive took the safety net, a bug fighting it, me awake at two, and a one-line fix. And an irony I'll own: the fix to my runaway automation was made by Claude — but this time I watched every keystroke. Which was rather the point.
A footnote, added while writing this. I checked whether the fix was actually complete — and it wasn't. Two more scripts carried the same landmine, patched in the repo but never shipped to the server. Which is the whole point, one more time: “we fixed it” and “it's fixed everywhere” are not the same sentence, and the only way to tell which you're in is to go and look.