← Back to field notes
Infrastructure5 min readSep 25, 2026

Real Bugs, Real Fixes: Debugging in the Open

What breaks when you build a network tool alone, and the recurring bug-and-security sweep habit that substitutes for a second engineer's review.

Part 4 of 10 in Building MistPulse: A Network Architect's Field Notes

Share:XLinkedIn
Real Bugs, Real Fixes: Debugging in the Open

Every engineering story that only talks about what worked is missing half the picture. MistPulse has shipped its share of bugs, and I've come to think that's not a mark against the project; it's what happens when you build fast, alone, and honestly. What matters more than avoiding every mistake is having a repeatable process for finding them before they cause real damage, and being willing to admit when something needs a second pass.

The rhythm of a solo bug and security sweep

Without a team of reviewers, I built my own substitute: a recurring habit of stepping back from feature work to run a deliberate bug-and-security sweep across recent changes. This isn't a vague "let me look things over." It's a checklist-driven pass: are there any regex patterns that could match more than intended? Any write path that doesn't validate its input? Any place where an error message might leak more detail than it should? Any code that assumes a happy-path response shape without handling the null or empty case?

These sweeps happen most rigorously right before anything gets deployed, especially anything touching the Supabase backend, since a mistake there has a much larger blast radius than a mistake in a single screen of the mobile app. The habit has caught real issues: overly permissive path patterns in the proxy's allowlist, missing validation on a device-rename input, and, more than once, a case where a newly added feature's error handling assumed the wrong shape for a failure response.

What matters more than avoiding every mistake is having a repeatable process for finding them before they cause damage.

A bug-and-security sweep checklist: input validation, error handling, no secrets in logs, allowlist coverage, write-path safety, regression checks

A generalization bug: when a feature outgrows its own assumptions

One instructive case came from a feature that started narrow and grew. A bulk-rename tool was originally built to handle only access points: rename a batch of APs at once by pattern, useful when standardizing naming conventions across a site. It worked well and got used often enough that the obvious next step was extending it to switches too, since the same operational need applied there.

The naive approach would have been to copy the AP-specific component, swap a few labels, and ship a near-duplicate for switches. I chose instead to generalize the underlying component to handle both device types through a shared interface, which is the right call for long-term maintainability, but it's also exactly the kind of change that introduces subtle bugs. Assumptions that were safe when the component only ever saw one device type (certain fields always being present, certain validation rules always applying) stopped being safe once a second, differently-shaped device type came through the same code path.

Catching that required going back to the same discipline from the last post: pulling real API responses for both device types side by side and checking, field by field, where the assumptions the original component made no longer held. A couple of them didn't, and the fix was straightforward once identified, but it wouldn't have been caught by just running the happy path with a switch and seeing that it appeared to work.

  • Generalizing a feature to a new use case means re-verifying every assumption the original version relied on
  • Side-by-side comparison of API response shapes across use cases catches drift that a single test case won't
  • A component that "appears to work" after a quick manual test isn't the same as a component that's been checked against edge cases for the new use case

Bugs that only show up on real infrastructure

Some of the most useful bugs to find are the ones that only appear on real, messy, production-like data, not the clean, small dataset you build against during initial development. A device with an unusual configuration, a site with an unusually large number of clients, a metric that returns an unexpected but technically valid value. Running MistPulse against my own home lab's real Mist deployment, rather than only against a minimal test setup, surfaced issues that a smaller, tidier test environment never would have.

This is part of why the "curl first" discipline from the previous post pays for itself repeatedly: the same live-testing muscle that verifies a new feature before building it is also the muscle that catches regressions when an existing feature meets a real-world edge case for the first time.

Fixing without a new build, when the fix doesn't need one

Not every fix requires shipping a new TestFlight build. Backend-only changes: a corrected regex in the proxy's allowlist, a validation rule added to an edge function, can be deployed directly to Supabase without touching the mobile app at all, since the app already calls those endpoints generically. Knowing which layer a bug lives in, and whether fixing it requires a client update or just a backend deployment, has saved a meaningful amount of iteration time across this project. It's also one more argument for the proxy architecture from Part 2: a real security boundary turns out to double as a very convenient place to patch bugs quickly.

The bugs so far have mostly been about the app talking to Mist. The next post turns to a much harder integration challenge: teaching the phone's own voice assistant to talk to Mist too.

Coming up next: Teaching Siri to Speak Mist: building native App Intents integration, and the real build failures that came with it

J

Written by

Jesus Hurtado

Network Architect and Cybersecurity Professional.

Comments

No comments yet. Be the first.

Leave a comment