My AI Watcher Found the Bug 26 Days Before I Did. It Wrote the Fix Where Nobody Reads.

October 15, 2026 · Todd Deshane · 9 min read

Hackaday ran a project this week that I can't stop thinking about for the wrong reason. Henry Yuan wanted an AI agent to operate an iPhone. Instead of jailbreaking anything, he mirrored the screen to a laptop over AirPlay, fed the frames to the model, and plugged in an ESP32-S3 pretending to be a USB mouse and keyboard. With AssistiveTouch switched on, the agent has hands. No developer account, no Xcode, no Mac. A five-dollar microcontroller turned "the model can see what to do" into "the model can do it."

The same day, The Robot Report ran a piece on Analog Devices' new robotics hub, with a sentence that reads like a platitude until you've been bitten by it: even when a robot makes the right decision, executing it depends on sensor data, timely control signals, and enough power to drive the actuators. A decision with no actuator is not a decision. It's a diary entry.

I have an AI watcher on my sump pump. It makes decisions four times a day. This week I went and read what it had decided.

September 15, 12:06 pm

Here is the setup, briefly, for anyone new. A smart plug meters the pump. A deterministic monitor runs the relay. A second program, the "smart assessor," reads the monitor's log every fifteen minutes and classifies the situation. And on top of those, four times a day, a scheduled job runs Claude with a short prompt, the last hour of logs, and permission to run shell commands, and asks it to reason about the gestalt: the things a single-snapshot rule would miss. It writes a note to a file called sump_claude_reasoning.log. It has written 529 of them since June 2.

On October 11 I published a post about the assessor's flood alarm. It had warned "No pump run seen in 28 hours" while its own status line showed the pump drawing 495 watts. It raised that alarm 195 times in September, every one false, and emailed me seventeen of them. The cause was two log formats and a parser that understood only one. I was pleased with that post. It was a clean piece of forensics.

This is what the watcher wrote at 12:06 pm on September 15, the first day the alarm fired:

[2026-09-15 12:06:24] FLOOD_RISK WARN (14.2h no-run) is a FALSE POSITIVE:
sump_pump_monitor.log shows real pump runs at 09:13:34, 10:19:17, and
11:25:59 ... parse_monitor() in sump_assess.py only updates last_run_dt via
the TIER-duty-cycle regex 'cycle N: pump ran (...)' (line ~179); it never
matches the plain NORMAL-state log format 'Pump started running (...)' /
'Pump stopped after N min (...)' ... This is the same bug class as commit
f52c96c (stale last-run timestamp) but that fix covered TIER duty cycles
only ... recommend extending the regex/logic to also capture 'Pump started
running' lines. No ntfy sent: fast layer already alerted on this at
09:54:09 (would be a dup)

That is my October 11 post. The function, the line number, the two log dialects, the fix. It even cross-referenced a June commit that had fixed the mirror-image bug in the other program, which I hadn't noticed. Line 179 of the assessor today still reads cycle \d+: pump ran. The watcher said it again at 6:05 pm, again at midnight, and three more times over the following two days: six of its eleven notes across September 15, 16 and 17 carried the diagnosis. Then the alarm stopped firing, because the pump went back into its timed mode, and the watcher stopped mentioning it. When the alarm came back on September 25, it didn't re-diagnose. Twenty-six days later I found the bug with a grep and called it mine.

Correction to the October 11 post. The finding was not first mine. The AI layer on the same host diagnosed it in full on September 15, with a commit reference I lacked. The post stands as written; this is the record of who got there first.

Who reads the log

Nobody, it turns out, and I can say that precisely. grep -rl sump_claude_reasoning ~/home-assistant returns nothing: no script, no service, no digest reads the file. The daily status email reports whether the watcher service is active, which it has been 29 mornings out of 29, and not a word of what it said. My own weekly writing cycle started tailing the file on September 11, and reads about the last two notes.

The watcher does have one effector. Its shell access lets it curl a push notification to my phone, and the prompt tells it to do so when something rises above what the fast layer already covers. It has done that in 9 of 529 notes. On this bug it did it once, at 12:05 pm on September 17, two days in: "Sent one corrective ntfy." I don't remember seeing it, and there's no record either way. The other five times it decided against. Read the last line of the note again: No ntfy sent: fast layer already alerted on this, would be a dup.

That rule is the whole story. The watcher deduplicates by topic. The fast layer had already alerted about a flood risk, so the watcher's note about the flood risk was a duplicate. But the fast layer's alert was false and the watcher's note was the correction. Deduplication keyed on subject suppressed the one message that said the subject was wrong. I wrote almost exactly this sentence a month ago about my guardian's email dedup, which silenced four escalating overtemp cutoffs because they shared a subject line with the first. Same shape, different component, and I didn't see it until I read the file.

The whole ledger

Having read one note, I read them all. 179 of the 529 contain the word "recommend." Grouped by subject, with whether anything happened:

RecommendationNotesFirstLastStatus
Set a DHCP reservation for the plug's MAC address117Jun 3Oct 6Not done. The plug has changed address eight times since.
Inspect or replace the check valve163Jun 4Oct 14Not done. Day 230 on my own checklist.
Suppress the float-unstick routine during high inflow36Jun 3Oct 15Not done, and wrong about the mechanism (next section).
Fix the Python path in the launchd job "on this Mac"17Jun 5Jul 23Moot. The prompt told it it was on a Mac. The host is Linux.
Fix the assessor's parser, line 1796Sep 15Sep 17Correct. Rediscovered by me Oct 11. Still queued.
Raise the 50 °C temperature warning, it's noisy1Jul 10Jul 10Not done. The guardian later drove the plug to 60 °C; the warning wasn't the problem.
"Go physically check the plug, outlet and router NOW"1Oct 5, 12:06Not sent ("guardian is already paging hourly"). I fixed it at 7:49 pm from a different process.

Seven standing recommendations. Zero actioned. One exactly right, one moot, one wrong, four reasonable and ignored. And the one that was right is the one it said least. Repetition count tells you how long a condition persisted, not whether the advice about it is any good. The DHCP reservation was recommended 117 times and is cheap and correct and I have never done it, and in late September the plug walked through four addresses in eleven minutes while the watcher said, again, DHCP reservation.

Last night's note, and the trigger that doesn't exist

At 12:05 this morning the watcher wrote this about a routine it has been complaining about since June:

[2026-10-15 00:05:40] ... One benign false-positive at 23:41-23:43: monitor
ran its 6x UNSTICK rapid-cycle sequence then held ON 60s and declared
'UNSTICK FAILED' because the pump was still drawing 493W -- this was just
sustained high inflow, not a stuck float ... recommend a human tune the
stuck-float heuristic to require near-zero watts (not just 'still running')
before triggering UNSTICK

There is no stuck-float heuristic that triggers the unstick. The line that started the sequence at 11:41 pm reads TIER_3: periodic unstick attempt (last was 4.0h ago). It's a timer. Every four hours in the top tier, unconditionally, the monitor rapid-cycles the motor six times and holds it on for a minute. Of the 1,030 attempts in the log, 968 were that timer. The watcher has watched this happen three times a day for four months and built a model of a watts-based trigger that would make sense, and then recommended tuning it, 36 times.

The fix it proposes is also upside down. The routine's own success test is "the pump reads idle after the hold." Requiring near-zero watts before starting would fire the remedy only when its success condition is already met. The watcher is right that the routine is useless; I measured that myself in September, a 0.68% pass rate with every pass undone inside 93 minutes. It is wrong about why, and it has been confidently, fluently wrong about why since June 3.

I want to be fair to it. It's a small model on a fifty-cent budget, reading an hour of logs with a prompt I wrote badly. Its six correct notes all cited a file, a line number and the specific log lines that contradicted the alarm. Its 36 wrong ones never cite a line of code, because there's no line to cite. That's a filter I could have applied, and didn't, because I wasn't reading.

What I'm changing

Three things, none of which needs the pump monitor restarted.

An AI layer that can diagnose but not act, writing to a file nobody reads, is not monitoring. It's a very expensive way to be right in private. Before you add a model to your stack, decide what it is allowed to do when it's right, and who will know.

The audit question

If you've put an LLM anywhere near your operations, it has a file like mine. The question is not whether it's smart.

Where do your AI layer's recommendations go? Who read the last one? And when it disagrees with your existing alerts, which one wins?

Mine went to a log. Nobody read it. The alarm won, every time, because the correction looked like a duplicate of the thing it was correcting. Yuan's iPhone agent got hands for five dollars. Mine has had hands all along, in the form of a shell and a push-notification endpoint, and a prompt that taught it to keep them in its pockets.

The inspection is day 230. Tomorrow's forecast is 21 millimeters, the first real rain since Monday, and the watcher will write about it at 6:05, 12:05, 6:05 and 12:05. This time I'll read all four.

Who reads your AI's notes?

I build and audit monitoring for small buildings: sump pumps, boilers, compressors, anything with a motor and a power draw. If there's a model in the loop, the audit starts with where its output goes and what it was told not to say.

See the Physical AI practice