From Error Alert to a Fix PR From My Phone: Building an On-Call Bot in a Day
I built a homemade Telegram bot and scrapped it the same day. A setup that fixes server errors from my phone without opening a single inbound port.
I was running three services on my own, and the only way to know an error had happened was to go look at the logs. In one night I built an on-call bot that alerts my phone and gets a fix PR out with a single button press. But sending an alert means catching the error first, and once I started writing that collection code myself, what I was actually building stopped being clear.
What I set out to do
I was up to three services, and the problem was when I’d find out about errors. I only knew if I was looking at the logs, so if I was out, hours would pass without me knowing.
Yesterday evening I started like this.
“I want to get backend error alerts through a Telegram bot. And I want the Claude Code CLI to fix the error and open a PR.”
The goal wasn’t just receiving alerts, but getting a fix PR out with one button press from my phone.
How I asked
First I built a Telegram bot myself. It receives messages via long polling, parses commands, and runs Claude Code on the Mac mini. Finished it this morning.
There was one minor bit of fumbling along the way. I already had a bot from before, but I couldn’t remember where I’d put the token, so I ended up creating a new one. Watching this kind of thing pile up, I need to sort out secrets management at some point.
Where it broke
I threw away what I’d built that same night.
The homemade bot’s problem was on the error-collection side. To send alerts to Telegram you first have to catch the errors, and that means attaching collection code to each of backend, frontend, and mobile. Three stacks, different languages. Once I started writing that myself, I wasn’t building a bot, I was building an error tracker.
That’s a thing that already exists. So I consolidated collection onto Sentry, and kept only the part I’d built: “receive what Sentry reports and run Claude Code.”
How I fixed it
I split the structure in two.
relay: a Cloudflare Worker. Receives the Sentry webhook and forwards it to Telegram. agent: a Go daemon running on the Mac mini. Reads Telegram via long polling, and when a command arrives, runs Claude Code.
The most important thing about this split is that not a single inbound port is opened. The Mac mini only reaches out to the Telegram API and polls. No path from the outside into the home network gets created. That’s the part that worries me most about running a server on personal hardware, and long polling erases it entirely.
And then tonight, the first real incident came.
[level=PANIC] runtime error: invalid memory address
Got the alert on my phone, pressed the button to run Claude Code, and checked the PR that came up, on my phone. It was 11:30 at night and this is what I was doing.
“What are you up to?”
Just a one-liner I threw out because I was curious what the agent was doing, but as a summary of the day, nothing beats it.
The same day I also removed the password gate on the web service. Now anyone can get in, so errors can happen at any time. Building on-call one day earlier turned out to be lucky ordering.
Lessons
- Before building it yourself, check once: “what am I actually building right now?” I set out to build a bot and was building an error tracker. Catching it within a day is the silver lining.
- When using personal hardware as a server, outbound polling is the answer. Don’t open inbound, and half your security worries disappear.
- Alert and action have to be wired into one line for it to actually get used. If only the alert arrives, it becomes “I’ll look later”; if you can press a button on your phone, it gets handled on the spot.
- Collection is someone else’s; judgment and action are mine. That boundary was the core of this design.
- Throwing away what you built the same day stings, but if a one-day loss bought a change of direction, that’s cheap.