If you use an Apple Silicon Mac on Wi-Fi, you probably know the pattern. Everything is fine. Then it isn't. Websites won't load. The Wi-Fi icon still shows connected. Ping times spike to 500ms or just time out entirely.
The fix is always the same: toggle Wi-Fi off and on, flush the DNS cache, renew the DHCP lease, maybe restart mDNSResponder. Four steps, two minutes, done. Then it happens again three days later.
I ran through this sequence probably twice a week for six months before I decided I'd wasted enough Tuesdays on it.
What NetWatch Does
NetWatch is a background daemon that runs silently on macOS. Every 30 seconds it checks:
- Interface state — is the Wi-Fi interface up and associated with an access point?
- DNS resolution — can it resolve a known good domain?
- Gateway reachability — can it ping the default gateway?
- Packet loss — is there sustained packet loss above a configurable threshold?
If any of those checks fail, it runs the corresponding fix sequence automatically:
- DNS failure → flush cache, restart mDNSResponder
- Gateway loss → renew DHCP
- Wi-Fi down → power cycle the interface
- Sustained packet loss above threshold → full network reset
The whole fix sequence takes under 10 seconds. Most of the time you don't notice it happened.
Why Not Just Use a Script?
A cron job would handle the timing, but it can't adapt. If the DNS check fails but the gateway is reachable, you need a different fix than if the interface is down entirely. NetWatch has a decision tree: it runs the minimally invasive fix first, verifies whether it worked, and escalates if not. A cron job running a fixed script would just run the nuclear option every time.
The other thing a cron job can't do well is rate limiting. I added a 5-minute cooldown between fix attempts for any single failure category. Without this, a prolonged outage would cycle the Wi-Fi interface every 30 seconds — which is more disruptive than the original problem.
The Security Part
networksetup commands on macOS require admin access. The naive approach — store the admin password in a config file or keychain — felt wrong. A rogue process with access to that stored credential could do a lot of damage.
NetWatch handles this differently: it prompts for your admin password at launch (once), holds it in memory only for the duration of the session, and clears it on exit. No persistent storage of credentials anywhere. If the daemon restarts, you enter the password again.
This is a modest improvement, not a complete solution. The password is in process memory, which is not zero risk. But it's substantially better than a plaintext config file or an item in the system keychain that any app can query.
Packaging It
The Python script was the easy part. Packaging it as a native macOS .app that non-technical users could double-click was harder.
I used py2app to bundle the script into a standalone app with a status bar icon. The first version required installing Python and all dependencies manually — not viable for anyone who just wants the tool to work.
The final version is packaged via Homebrew:
brew tap cyrillical00/netwatch
brew install --cask netwatch
Homebrew handles the Python runtime, dependencies, and placing the app in /Applications. The user experience is: run one command, the app appears, it works.
Getting the Homebrew tap set up was its own adventure. The cask formula has to specify a SHA256 hash of the .dmg, which means every release requires rebuilding the DMG, computing the hash, updating the formula, and tagging the release. I've since automated that with a GitHub Actions workflow, but the first release I did by hand and it took longer than writing the original script.
Results
Since deploying NetWatch on my machine: zero manual network interventions in four months. The daemon has run the fix sequence 23 times — mostly DNS flushes, three DHCP renewals, one full interface power cycle. I know this because it logs every action to a file.
The fix I'd run manually twice a week now happens in the background without interrupting me. That's the entire goal.
The project is open source. If you're experiencing similar issues on Apple Silicon (and you probably are — it's a known class of problem), the install instructions are in the README. Zero telemetry, no cloud dependency, everything happens locally.