System Active // V3.0.0
LOC: 23.8103° N
All posts
Jul 6, 2026·6 min read

I Built a Telegram Remote for Claude Code, Then Let It Build Itself

StoryAI AgentsAutomationtmux

For a while now I've been coding from my phone during commutes, SSH'd into my VM through Termius, running Claude Code directly in a mobile terminal. It works, technically. It is also miserable. A full-screen TUI redrawing itself on a six-inch screen, permission prompts you have to squint at, losing your scroll position every time the pane repaints. Termius earned its keep, but it was never going to be a real way to work.

Then I had dawats three days straight, Wednesday through Friday. If you haven't heard the word, a dawat is basically a hosted invitation, a family or community gathering you actually have to show up and be present for, not something you sneak a laptop open during. Three consecutive days of that meant close to zero real computer time, and my project pipeline (the World Cup bracket app, cv-roast, a few others) does not pause for anyone's social calendar. Phone-only coding stopped being an occasional annoyance and became the only option I had. That's the itch that actually got scratched. Termius was the wrong tool for that job, so I built the right one: Tgmux, a Telegram bridge to Claude Code sessions running in tmux on my VM.

The core idea: don't fight the TUI, read it

The first real design decision was refusing to treat this as a headless automation problem. A one-shot claude -p call takes a prompt and gives you an answer, no follow-up turn, which is useless for an actual build conversation where the agent asks questions and you reply hours later from a different city. What I needed was a persistent interactive claude REPL, kept alive in a tmux session per project, that I could talk to asynchronously without ever needing to reattach a terminal by hand.

Telegram (phone)  <->  Tgmux daemon (Python, VM)  <->  tmux: proj-<slug>
                                                          claude REPL
                                                          dev server :30xx

Each project gets its own proj-<slug> tmux session with an allocated port in a pool, so five agents can be mid-build at once without colliding. The daemon never makes me SSH in directly. My Telegram messages become tmux send-keys, and whatever the agent renders back becomes a Telegram message.

That last part is the genuinely interesting bit. Claude Code is a full-screen TUI, so raw pipe-pane streaming is redraw soup, cursor jumps, partial repaints, the same line rewritten five times a second. Instead, the daemon polls capture-pane every two seconds, strips it down with a battery of regexes (ANSI codes, box-drawing borders, spinner glyphs, "esc to interrupt", token counters, the live input-box echo of whatever I'm mid-typing), diffs the cleaned result against the last snapshot it relayed, and sends only what's new, coalesced so I don't get spammed one Telegram message per keystroke. The snapshot baseline is saved to disk per agent, so restarting the daemon never re-sends old output or silently drops new output, it just picks the diff back up where it left off.

Two more pieces make it feel like a real remote control instead of a chat log:

  • Question detection. The daemon watches the stable tail of the pane for actual blocking prompts, permission dialogs, (y/n), numbered menus, as opposed to normal conversational text, and only pings me with quick-answer buttons when the agent is genuinely stuck waiting. Ordinary progress updates just arrive quietly.
  • Mode control by reading the status line. Claude Code shows its current mode (normal, auto-accept, plan) in the status line at the bottom of the pane. There's no API for "set mode directly," so /mode auto reads that status line, sends Shift+Tab, re-reads the status line, and repeats up to four times until it actually reports back "auto." It's controlling a TUI blind, entirely by reading its own rendered output back and closing the loop against that.

Shipping is gated on purpose: /push commits and pushes a dev branch (creating the GitHub repo the first time) and hands back a Vercel preview URL, but going to production needs an explicit /deploy and a Telegram confirm button. The CLAUDE.md injected into every project also tells the agent directly never to deploy on its own. Every command, push, and deploy gets appended to a JSONL audit log, and the bot answers exactly one Telegram user ID, silently ignoring everyone else.

Then I used it to build the rest of itself

Once the base loop worked, spawn a session, relay text both ways, I stopped opening a laptop for this project at all. I adopted Tgmux's own tmux session as just another agent inside itself and kept building it entirely from the phone, the same way I'd build any other project through it. Every feature after the first version was requested and shipped through the exact same Telegram thread it was improving:

What I typedWhat shipped
"add a uptime command that shows daemon uptime"/uptime
"make a command to kill agents"/kill, /killall
"add some emojis before the commands so they're easily readable"emoji-prefixed command list
"name this project Tgmux, push to GitHub, add a README"repo created via gh, README written
"can i resume a session if i kill it? ok add revive"/revive, respawns a killed agent with --continue
"add a /merge command, not all projects have vercel"/merge, a git-only dev to main release
"I saw a button, will you improve the ux"quick-answer buttons on prompts, tap actions on /list
"/mode auto should trigger auto directly, not blind cycling"/mode takes a target and reads the status line to confirm it

One of those had a real near miss behind it. Partway through, I attached a couple of test photos to see how file uploads worked, and one of them nearly ended up swept into a git commit before I caught it and reverted. That's what actually prompted the fix right after: every project's file upload now auto-adds its incoming/ folder to .gitignore, because that folder is for feeding files to the agent, not for publishing them.

Everything in that table shipped in one sitting, on the same day, entirely dictated from a phone through the exact bot being modified. The tool got good enough to build the rest of itself before I ever needed to sit at a desk for it.

Where it's at now

It stopped being a travel workaround and became the default. Right now, in one VM, Tgmux has live or recent sessions for this portfolio, the World Cup bracket app, cv-roast, and a couple of other side projects, switching between them is just /switch <name> or replying to whichever agent's message came in. If you want the honest proof of how far that went: this post itself, the request to write it, the back-and-forth about what to include, all of it came in over the same Telegram thread, to an agent running inside a Tgmux session named portfolio. The dawats are long over. I still haven't gone back to coding any other way.

Keep reading