17 - Sep - 2026

Claude Code found 5 bugs in my project that I never would have caught

I’ve been running a home lab for a while now, juggling ESP32 controllers, a Home Assistant voice satellite, several self-hosted Docker containers, and Next.js apps, and having done all of that, I thought I knew my own systems pretty well. Turns out, I did not.

There were a few things off in some of my ESPHome projects, and I had spent months chasing these bugs. Then I brought in Claude Code, and it found bugs I never would have. Each of these bugs had a root cause so small it was almost insulting, but each one took hours, sometimes days, to actually track down.

Twenty Wi-Fi drops a day, and none of them were Wi-Fi

ESPHome was re-scanning for an access point that didn’t exist

I made a $10 climate control unit that controlled my apartment’s air conditioners using an ESP32 C3 Supermini. The issue was that it kept falling off my Wi-Fi network more than 20 times a day. It looked like a signal issue, so I checked battery voltage, power sources, swapped the USB cable, and even checked my guest network’s client isolation settings.

The actual culprit was ESPHome’s automatic post-connect roaming feature that keeps re-scanning for a better access point, even though I only have one access point in the house. It kept tearing down a perfectly good connection for nothing, and every reconnect attempt collided with an old Auth Expired bug, spiraling into multi-minute retry storms.

The final fix was just two lines of YAML: enabling RRM and BTM. Finding it, though, required a serial capture, since Home Assistant’s history API only showed that the device dropped, never why.

A wrong domain, a doubled prefix, a stale trigger, a trailing newline

Climate Control unit automation in Home Assistant.
Screenshot by Yadullah Abidi | No Attribution Required.

My climate control device relies on a corresponding automation that I made in Home Assistant, except it never worked, and it took peeling back four separate layers to understand why. First, I’d written a lookup expecting a test_sensor domain in Home Assistant. But since that’s an ESPHome-internal naming convention, the entity actually lived under sensor, and my lookup silently resolved to nothing every single time.

Second, I’d accidentally double-prefixed an entity ID by repeating the device name in the sensor’s own name field, so instead of collapsing cleanly, the string concatenated into something that matched nothing. Third, my numeric_state trigger only fires on crossing a threshold, not on already being past it, so switching to a room that was already at a higher temperature did nothing.

Last but not least, a multiline Jinja if-block was leaking a trailing newline into my entity ID string, so it was comparing two wrong strings and failing the match. Just when I thought I’d fixed everything, I realized the live automation in Home Assistant’s UI had been hand-pasted in and never updated, so it kept running the old broken logic no matter what I pushed to my repo.

A voice assistant that went deaf, then talked to itself

Three failures stacked on top of each other, one board

Another device that I had a nightmare of a time debugging was my Home Assistant voice satellite on an ESP32-S3, which had three separate failures stacked on top of each other. It would go completely deaf after hearing its wake word exactly once per boot, which needed an explicit microphone binding and a very specific startup order to fix.

Then, once that was taken care of, it started talking to itself in an infinite loop roughly every five and a half seconds, because the code that handles session-end was misinterpreting its own forced stop command as the user finishing a sentence, prompting it to reopen the mic. This took a suppress_follow_up flag to fix.

The last failure, total silence, took the most humbling path to resolution: dropping all my YAML, running a bare-metal Arduino tone test, and putting a multimeter on the amp’s pins, where I found a loose jumper connection that a probe nudge temporarily fixed. I had also dismissed my own hunch that switching wake words would fix the problem because it switched wake words mid-test when one of the wake-word model files was corrupted.

One npm command was quietly killing the database

npm swallows SIGTERM. Next.js doesn’t get the memo

My self-hosted Next.js app, backed by PGlite, started throwing RuntimeError: Aborted() on every login attempt, and the data directory was unrecoverable. The cause was traced back to a single line in my Dockerfile: running npm start instead of next start directly.

NPM swallows and delays SIGTERM signals to its child process, so every container restart was killing PGlite mid-write with zero graceful shutdown. Swapping to next start as PID 1 and adding a proper SIGTERM handler solved it permanently.

The fix from one bug became the wrong answer for another

A loose wire, not a bit-depth mismatch

ESP32 voice assistant amp board.
Image taken by Yadullah Abidi | No attribution required.

Another, more hardware-leaning bug Claude Code helped me figure out was garbled audio on a bone-conduction earpiece project that I was working on. The problem was mismatching bit depths between the mic and amp I was using.

Similar to the voice assistant’s jumper fix, the real issue was a loose mic wire on my custom 3D-printed breadboard, evidenced by the raw audio dump being mostly rail values. Once I reseated the wire, an A/B test proved the bit-depth mismatch never really mattered, disproving a rule I’d been treating as gospel.

Claude Code didn’t guess. It just looked closer

When a bug seems impossible, verify the smallest assumption first

None of these fixes required a brilliant breakthrough once the real cause was visible. That is exactly what made them so frustrating. A two-line Wi-Fi change, an invisible newline, a loose jumper, and one poorly chosen Docker command caused failures that looked much bigger than they were.

Claude Code did not magically replace testing or hardware debugging, but it was remarkably good at challenging my assumptions, following strange behavior through the codebase, and surfacing connections I had missed. The biggest lesson is simple: when a bug seems impossible, stop trusting what you’ve built and verify the smallest assumptions first.

Leave a Reply

Your email address will not be published. Required fields are marked *