My Internet Dropped 632 Times, So I Built a Dashboard for the Cable Guy

My internet went down 632 times in two days. I know the exact number because I built a monitor out of spite, and then a dashboard, and then I sent an AI to call Comcast.
Quick roadmap:
- How a perfectly boring connection turned into a strobe light
- Why “the internet is down” is five different problems wearing a trench coat
- What a cable modem writes in its diary when nobody’s reading
- The AI phone call that ended with the AI asking for a grown-up
- A dashboard with an audience of exactly one technician
- What’s still held together with tape
- How to set up all three pieces yourself, why they’re built the way they are, and why they’re safe to leave running
It worked for a year and a half
For about eighteen months my internet was the best kind of utility: invisible. Then, on the evening of October 17, it started blinking. Ten seconds down, back up. Forty seconds down, back up. Video calls froze mid-sentence and left me looking thoughtful for longer than I’ve ever looked thoughtful in real life.
I did what everyone does. I rebooted the modem. I replaced the Ethernet cable. I glared at the eero app, which showed a cheerful green dot the whole time, the way a dog looks at you right after eating something it shouldn’t have.
Then I did what I do instead of calling customer support: I opened Claude Code and said, more or less, “find out whose fault this is.”
Its first draft of the monitor assumed I was on Verizon. I am not on Verizon. The second commit in the repo is titled, with great dignity, retarget from Verizon to Comcast Xfinity. We were off to a strong start.
“The internet is down” is five problems in a trench coat
When the internet drops, any of five things might have failed, and they all feel identical from the couch:
this computer -> eero gateway -> modem -> Comcast hop -> internet -> DNS
netmon probes every link in that chain every five seconds and blames the first one that broke. If the eero stops answering, it’s my house. If the eero is fine but the modem isn’t, it’s a cable or a modem reboot. If the modem answers and nothing past it does, it’s the coax, which is to say Comcast.
That distinction is the whole point. “My internet keeps dropping” gets you a script that ends with “have you tried restarting your router.” “The modem stays reachable on the LAN while the first Comcast hop stops answering, 485 times” gets you somewhere else.
It’s plain Python with no dependencies and no root, and it’s happy on a laptop wired to the gateway. I ran it under caffeinate so my Mac wouldn’t nap through the evidence.
The verdict came back fast: ISP. Over and over. My house network was innocent, and for once I had the paperwork to prove it.
My modem keeps a diary
Here’s a thing I didn’t know: a Motorola MB8611 keeps a detailed event log of its own suffering, behind a login page at 192.168.100.1 that nobody ever visits. So netmon learned to log in every 60 seconds (password from the macOS Keychain, never the repo) and read it.
The diary was bleak. Over about two days the modem logged 7,413 failure events, including:
- 4,678 “Started Unicast Maintenance Ranging - No Response received - T3 time-out.” In English: the modem shouted upstream and Comcast didn’t answer.
- 1,267 downstream OFDM profile failures, mostly on one channel up at 957 MHz.
- 374 “UCD invalid or channel unusable,” which is marked Critical and also extremely relatable.
The signal numbers were worse. Downstream power sat between −8 and −15 dBmV on every channel, when you want it within about ±7. The 957 MHz channel was racking up roughly 10,000 uncorrectable codewords per second, which is the modem’s way of saying it received a lot of data and understood none of it. A few channels around 620 MHz swung up and down in SNR from minute to minute while their neighbors held steady, which smells like noise leaking into the line. Possibly 600 MHz cell service, though I’m a guy with a Python script, not an RF engineer.
Two things about the modem itself made me laugh, eventually:
- Its clock drifts. After one reset it jumped a full hour backward, so its diary entries were time-traveling. netmon now notices the offset and corrects for it, because you can’t line up “the internet died at 3:29” with “the modem panicked at 2:29” and call it evidence.
- Its error counters occasionally report physically impossible jumps, as in more uncorrectable codewords than the channel could carry in that time. The fix is a commit called Ignore impossible jumps in the modem’s uncorrectable counters, which I’d also like as a life motto.
I sent an AI to call Comcast
I built Banjo, an open-source assistant that makes real phone calls. This felt like its moment: hand it the evidence, have it call Xfinity, book a technician, and never hear hold music myself.
Call one got as far as the phone menu, which wanted the phone number on the account. I hadn’t given Banjo one. It escalated politely, which is technically correct and practically useless.
Call two was the one I’ll be telling at parties. The IVR said “para español oprima dos,” and Banjo replied, in English, “I’m standing by.” It entered the phone number, the IVR found my account, and then Banjo went completely silent. For three minutes the IVR kept trying. It asked nine times. It offered “press zero for an agent” twice, practically begging. Then it gave up:
“I’m sorry, I still didn’t hear anything. I can’t assist you any further.”
I have been dumped with more warmth. It turned out Banjo was playing its key-press tones as audio on the same channel as its voice, and every time the IVR started talking, Banjo’s barge-in handling politely flushed its own tones, so a menu that never stops talking never heard a single button press. That’s a real bug, now written up in detail, and it’s the kind you only find by letting the robot call the robot.
Banjo also allows three calls per number in 24 hours, a rule I wrote to stop it from harassing businesses. It was now stopping it from rescuing me. I respect the guardrail. I resent the guardrail.
Call three is why I built Banjo in the first place. I sent it off and went back to my evening. For the next twelve minutes it handled the part of customer service I hate most, and I didn’t hear a second of it.
It sat through the pitch for a free Xfinity Mobile line and the Spanish option. It told Xfinity’s own AI assistant it wanted technical support, gave my phone number, confirmed my account and asked for a human. Then the robot-on-robot portion of the evening began.
Xfinity’s assistant ran a “modem health check” and asked whether I’d tried unplugging the modem for ten seconds. Banjo said the customer wasn’t present and the evidence pointed to the line. The assistant asked again: “Just say yes or no.” Banjo said no. The assistant asked a third time and offered to follow up in ten minutes. Banjo said no again and asked, again, for a human. That’s three rounds of “have you tried turning it off and on again,” and I wasn’t in any of them.
Then a real person picked up and opened with my favorite line in the whole transcript: “OK, Assistant.” Banjo introduced itself as an AI calling for me and laid out the case: the dates, the outage times, the CM-STATUS codes, the downstream power and the misbehaving 957 MHz channel. The agent started a ticket and confirmed there’d be no charge if the problem turned out to be on Xfinity’s side.
Then the agent asked for the last four digits of my mobile number. Banjo declined, which is exactly what I’d told it to do: identity questions come to me. When the agent said the rest might take another 10 to 20 minutes, Banjo politely pointed out that was a long hold and offered to put me on instead. Twelve minutes in, the agent said “connect me with Steve,” and that was the first moment I had to deal with Xfinity at all.
Across all three calls, Banjo spent about seventeen minutes with Xfinity’s phone tree and its AI assistant. I spent none. That’s the whole idea: the robot handles everything up to the moment a real human genuinely needs the real me.
So I picked up the call, and what came next was the reason I’d built netmon in the first place.
I want to say up front that I feel for the people who answer these phones. They’re handed a script, a queue and a lot of angry people, and I was about to become one of them.
The agent wanted me to do the basics. Reset the router. Reset the Wi-Fi. Power-cycle my eero 6+ mesh. Reboot my computer. Yes, that’s the same reboot Banjo had just turned down three times. I’ve been on the internet long enough to know these steps work a lot of the time, which is exactly why they’re on the script. But I already knew where the problem was. I had a tool that had watched every link in the chain every five seconds for a day and a half, and it said, over and over, that everything on my side was fine and the trouble began where Comcast’s network did.
I asked them to look at the line before sending anyone. They wouldn’t write down what I was telling them, and they wouldn’t take my data files. I’d brought a spreadsheet to a phone call, and the phone call didn’t have a slot for spreadsheets.
I’ll admit I got irate. Not my finest moment, and not the agent’s fault.
I asked for an expedited visit the next day. The earliest they could do was two days out. In between I racked up more than five hours of downtime, and on Monday I couldn’t join video calls at all. My coworkers got a lot of “sorry, you froze, can you repeat that?” and then, eventually, nothing.
A dashboard for an audience of one
I had data, so naturally I built a dashboard. Its target audience was one person: the technician who’d walk in the door.
It runs locally, with no CDN or cloud, because a dashboard about your internet being down that needs the internet would be a very specific kind of joke. It has:
- A giant green or red banner, so I can see “is it down right now” from across the room.
- A technician summary at the top, written the way you’d brief a tech: when it started, how many outages, how many lined up with modem failures (297 of the first 300, within seconds), what the signal levels are, and a short list of what I’d like them to check, which was the drop, the tap, the ground block and any splitters.
- Outages per hour, modem errors per hour, power and SNR over time, uncorrectable codewords per channel, and upstream transmit power.
I also wrote up a one-page ticket for Xfinity with all of it: 300 outages and 83.8% uptime in the first 20 hours. Eight to 29 outages every hour, including 2 a.m., so it wasn’t my neighbors streaming the game.
There is something deeply satisfying about handing a support person a page that already answers “have you tried restarting it?” Yes. Here’s a chart.
The plot twist
At exactly 5:40 AM Eastern on Tuesday, October 20, my internet started working. No technician, no phone call, no warning. The modem dropped and re-registered without rebooting, logging “TFTP Request Retries exceeded, CM unable to register” on the way, which is the modem equivalent of hearing someone in a hard hat say “okay, try it now.” The upstream T3 timeouts stopped cold.
I’d love to tell you it was fixed. My dashboard said otherwise. The downstream power was just as low as before, the uncorrectable errors were still piling up at the same rate, and one channel was still blinking in and out. The errors were all still there. Comcast’s side had simply started putting up with them. Something upstream had changed, maybe on the node, but the problem in my line hadn’t gone anywhere.
The technician, who is my new favorite person
At about 10:30 AM a wonderful tech arrived.
I showed him the dashboard, half expecting the polite face people make when you show them your hobby. Instead he got it right away. He said it mirrored the same kind of tool they have in their own apps. I have never felt so validated by a man holding a signal meter.
We tested the signal coming into the house, then the signal at my modem on the third floor, and we both watched it drop. The line was fine where it entered the house. Somewhere between the garage and the third floor, inside my walls, a bad coax connection was eating my signal. (So much for my 600 MHz cell-tower theory. It wasn’t the airwaves. It was my house.)
That left two options:
- Rip up the walls to find the problematic connection, somewhere in a house full of finished drywall.
- Bypass the whole thing and put the modem and the eero gateway in the garage, where the line comes in, and broadcast the Wi-Fi from there.
I chose option two, for reasons that will be obvious to anyone who has ever patched drywall.
The netmon log of that hour is my favorite part of the whole data set. From 10:40 to 11:48 it reports outages it had never seen before: LAN, modem link, no route at all. For once it wasn’t blaming Comcast, because a professional was unplugging my house on purpose.
Then the numbers changed:
| Before (third floor) | After (garage) | |
|---|---|---|
| Downstream power, median | −9.6 dBmV | +0.7 dBmV |
| Downstream power, range | as low as −15 on locked channels | −2.7 to +1.2 dBmV |
| SNR, median | 38.1 dB | 43.8 dB |
| Upstream transmit power, median | 44.5 dBmV | 39.9 dBmV |
| Modem failure events | 12,682 in about two days | 3 in seven and a half hours |
| Outages | 632 | 0 |
The downstream power went from “out of spec” to “dead center.” The modem doesn’t have to shout upstream anymore. Its diary, which used to read like a disaster novel, is now mostly blank pages.
As I write this, the banner is green. The internet has been up for over seven hours straight, which in this household currently counts as a national holiday.
The final tally
Over about 50 hours of monitoring:
| Outages | 632 |
| Total time down | 7 hours, 14 minutes |
| Uptime | 85.7% |
| Modem failure events | 7,413 |
| Calls Banjo placed | 3 |
| Calls Banjo completed without me | 0 |
| Commits | 19 |
85.7% uptime sounds respectable until you remember it’s a grade for staying connected, and that’s a B minus.
What’s still held together with tape
- The modem collector only speaks Motorola (MB8600 and MB8611). Other modems have their own login pages, each with its own personality disorder.
- The topology assumes an eero behind a Comcast modem. Discovery is flexible, and you can pass
--upstreamyourself, but the advice text is written for my house. - “Root cause” means “first link to fail,” not “the reason.” It points you at the right segment. It can’t climb a pole.
- My fix is a bypass, not a repair. There’s still a bad connection somewhere inside my walls. It’s just not on the path anymore. If I ever renovate, I know what I’ll find.
- The Wi-Fi now starts in the garage. The eero mesh carries it up to the third floor, and so far it’s keeping up. That’s on my list to watch with netmon, naturally.
- Banjo’s DTMF bug is diagnosed, not fixed.
Run it yourself
The code is on GitHub at shatch/netmon, MIT licensed. It’s three pieces that share one folder of logs: a monitor, a modem collector and a dashboard. Here’s how to set up each one, why it’s built the way it is, and why I’m comfortable leaving it running on my own network. If you’re going to run a stranger’s code on your home network, you should get that last part in writing.
git clone https://github.com/shatch/netmon.git && cd netmon
python3 netmon.py discoverThere’s nothing to install. It’s standard-library Python 3.8+ on macOS or Linux, with no pip install, no virtualenv and no root.
1. The monitor
Setup. Run it on a computer plugged into the main eero with a cable, the one connected to the modem. That takes Wi-Fi and the mesh out of the picture, so when netmon says the internet died, it wasn’t your laptop wandering out of range. If you want a second opinion, run another copy on a Wi-Fi laptop at the same time.
Start with discover. It works out the path on its own: the eero is your default route, the modem is either the second hop of a traceroute or one of the usual modem addresses (10.0.0.1, 192.168.100.1), and the “ISP hop” is the first router past your house that answers a ping. If it guesses wrong, --upstream and --isp-hop let you tell it. Turn off your VPN first, since a VPN hides the real path, which is roughly its job description.
Then let it run:
caffeinate -is python3 netmon.py run --notify # macOS: stay awake, notify on outage
nohup python3 netmon.py run --quiet & # Linux: run in the background
python3 netmon.py report # from another terminal, any timecaffeinate keeps the Mac from sleeping through your evidence. If it does sleep, the report counts that time as a monitoring gap, not an outage, so a closed lid can’t inflate your numbers. --notify raises a macOS notification for each outage. Install terminal-notifier (brew install terminal-notifier) and clicking the notification opens the event log.
Why it works this way
- Every 5 seconds, because most of my outages lasted 10 to 50 seconds. Check once a minute and you miss half of them and can’t time the rest.
- About ten probes at once, in parallel: ping the eero, the modem and the ISP hop. Ping three public DNS servers (Cloudflare, Google, Quad9). Open a TCP connection to port 443. Ask a DNS question through the eero and directly to 1.1.1.1. Fetch Apple’s captive-portal page. Running them in parallel means one slow probe can’t delay the others and blur the timing.
- The internet counts as “up” if any public check gets through. One flaky server can’t fake an outage. It takes all of them failing.
- Blame goes to the first link that broke. When the coax fails, the modem soon reboots and everything downstream fails too. The first failure is the cause and everything after it is fallout. That’s why “the modem stays reachable while the internet drops” carries so much weight in a support call.
- Two failed samples in a row before it calls something an outage, so a single lost ping doesn’t count. A real outage is at least about ten seconds.
- It re-checks the network layout every five minutes, because DHCP likes to shuffle addresses when you aren’t looking.
Why it’s safe. It uses the system ping and Python’s standard library, and nothing else. It needs no administrator rights, opens no ports, and sends nothing anywhere except the probes listed above, plus a short traceroute when it maps the path. Everything it records goes into plain CSV files in a local logs/ folder that git ignores, so you can read every byte it keeps.
2. The modem collector
Setup. This part currently works with Motorola MB8600 and MB8611 modems. Put the modem’s admin password in the macOS Keychain, test a single poll, then add --modem to the monitor:
security add-generic-password -s netmon-modem -a admin -w # prompts for the password
python3 netmon.py modem # one poll: log in, print channels
caffeinate -is python3 netmon.py run --notify --modemOn Linux, or anywhere without a Keychain, set NETMON_MODEM_PASSWORD in the environment instead.
Why it works this way
- It logs in the same way the modem’s own web page does. That’s a challenge-and-response handshake (HMAC-MD5, if you collect acronyms), so the password never crosses the wire in plain text.
- Once a minute, not every five seconds. The event log and counters change slowly, and the modem’s tiny web server is not a professional athlete. Even at once a minute it times out every hour or so, and the collector just logs in again a minute later. Polling more often wouldn’t tell you more. It would just give the modem a second job.
- It fixes the modem’s homework. It corrects for the clock drift, so modem events line up with outages, and it throws away the physically impossible counter jumps.
- It sorts each event into what a technician would check: upstream ranging (T3/T4), downstream profile failures, lost lock and so on. That way the dashboard can say “2,289 upstream timeouts” instead of showing a wall of DOCSIS.
Why it’s safe.
- It’s read-only. It asks the modem for exactly three things: the event log, the downstream channels and the upstream channels. There’s no code in the repo that changes a modem setting, so it can’t reboot the modem, change a setting or “optimize” anything.
- The password never touches the repo or the command line. It comes from the Keychain or an environment variable at runtime, and the
securitycommand prompts for it so it doesn’t land in your shell history. - The modem’s certificate is pinned. The modem uses a self-signed certificate, so the collector records its fingerprint the first time it connects and refuses to log in if that ever changes. Anything else on your network pretending to be the modem gets a polite refusal instead of your password. If you replace the modem or its firmware updates, delete
logs/modem-cert.sha256to trust the new one.
3. The dashboard
Setup.
python3 netmon.py dashboard # http://localhost:8765/
python3 netmon.py dashboard --host 0.0.0.0 # also reachable from a phone on your Wi-FiLeave the monitor running in another terminal. The dashboard reads the same log files, refreshes the charts every 30 seconds and checks the up/down banner every 5. Times are shown in your device’s time zone, and adding ?tz=America/New_York (or any time zone name) to the URL pins one.
Why it works this way
- It runs on your computer, with no cloud and no CDN. A dashboard about your internet being down that needs the internet would be a very specific kind of joke. It works with the coax unplugged, and that’s exactly when you need it.
- It’s one HTML file and a tiny Python server. There’s no framework, no build step and nothing to sign up for. The technician can read it on your laptop, which is exactly what happened.
- The summary at the top is written for the technician, in their vocabulary, with the checks I wanted done listed right there. A support visit is short, and I wanted the first thirty seconds spent reading my evidence.
Why it’s safe.
- It’s only reachable from your own computer by default. The server listens on
127.0.0.1unless you tell it otherwise. - It only answers requests addressed to this machine. That blocks a trick called DNS rebinding, where a malicious web page in another tab tries to read your local servers through your browser.
- It treats what the modem reports as untrusted. Channel names and event text from the modem are escaped before they’re shown, because the modem is a stranger too.
- It only shows data. There are no forms, no logins and no buttons that change anything, and it loads no third-party scripts or fonts and sends no analytics.
--host 0.0.0.0is a choice with consequences. It lets anyone on your Wi-Fi see your outage history. That’s fine for most homes, but don’t do it on a coffee-shop network.
One last warning: your logs contain your modem’s MAC address, which your ISP ties to your account. Share the charts, not the raw logs.
If your internet is blinking right now, you don’t need to be an RF engineer. You need a timestamp, a first-failing hop, and the modem’s own diary. Then the support script runs out of road a lot sooner.
And if you send an AI to make the call, give it your phone number first.
Filed under AI, Open Source, Technology.