TL;DR

I'm sure we've all experienced that feeling when we stepped away from our computer, expecting Claude to have diligently run the workflow, only to come back to disappointment to see it was stuck waiting on your input, a simple question that could have been answered when you were away. I set out to setup Claude to run my Claude workflows from my phone : trigger them, respond to mid-run questions, and get results without being at my computer. The solution, which I initially thought would be wasn’t a complex orchestration platform, OpenClaw,Heremes, or a WhatsApp integration, turned out to be a much simpler 300-line Python file, a Telegram bot, and a realization that my SKILL.md files work fine outside of Cowork/Claude Code.

I have several workflows I use regularly. For example an IR (investor relations') Scraper that downloads investor relations documents from any public company’s website. A SellSide Initiation pipeline writes equity research reports in six stages across multiple Claude API calls. A compile-wiki-node skill synthesises everything in my Obsidian vault about a company into a single compiled wiki style note. A summarize-investor-conversation skill turns voice transcripts of my investor meetings into structured notes with explicit desing to extract themes, company names etc. The conversation summarization logic here would be different say from other summarizing other calls where I am speakig about travels, music, food or something entirely different.

All of these run in Cowork or Claude Code. Fine when I’m at my desk. Not fine when I’m traveling, in a meeting, or on my phone:and a workflow has paused mid-run waiting for me to answer a question.

The challenge with Cowork

The immediate instinct was to intercept Cowork’s question prompts and forward them to my phone.

When Claude pauses in Cowork and asks a question, it renders a UI widget in the desktop app. There's no webhook, no notification event, no hook that fires to an external service. Cowork’s interruptions are UI events, not API events. Claude Code has a notification hook system, but it fires one-way: the reply path (phone back to Claude Code, to resume the paused workflow) has no bridge.

The thing that Claude didn't figure out early was that my SKILL.md files are prompts, and prompts are portable. Cowork is one runner: a desktop UI that reads a prompt and executes it. A Python script calling the Claude API directly is another runner. If you control the runner, you control the I/O channel.

Instead of trying to intercept Cowork, I could have Claude build a different runner entirely: one that uses Telegram as the terminal. The same SKILL.md content that tells Claude how to compile a wiki node or run a SellSide analysis works identically when passed as a system prompt to the Claude API from a Python script. The intelligence is in the file. The UI is interchangeable.

This reframe collapsed the problem. I didn't need to bridge Cowork to my phone. I needed a runner that natively used my phone as its I/O.

Why Not WhatsApp

The first plan Claude proposed involved Twilio’s WhatsApp Business API. This turned out to be more friction than the actual problem. WhatsApp Business API requires Meta Business Verification: a formal approval process that takes 1-3 days and sometimes longer. You also need a dedicated phone number not already registered as a personal WhatsApp account. There are per-message fees on top.

Telegram sidesteps all of it. Open @BotFather on Telegram, send /newbot, give it a name, get a token. Five minutes. Free. Fully bidirectional. The Python library: python-telegram-bot is mature and async-native. I didn't need to think about infrastructure.

Building remote access through my mobile

Claude helped design an agentic loop. The bot receives a Telegram message, calls the Claude API, handles whatever Claude wants to do: use a tool, ask a question, or finish, and sends the result back. It’s async throughout: the bot keeps polling for new messages while Claude works in the background.

Claude has five tools available in every workflow. ask_human pauses the workflow and sends me a Telegram message with a question; I reply, it resumes. run_command executes a shell command or Python script on my Windows machine, streaming progress updates every 20 seconds. read_file, write_file, and list_files give full file system access:vault notes, SKILL.md files, transcripts, and output folders.

The bot also has a skill registry: a Python dictionary mapping skill names to their SKILL.md paths. When I send /run compile-wiki-node, the bot reads the SKILL.md, passes it as Claude’s system prompt, and starts the workflow. Seven skills are registered: compile-wiki-node, summarize-investor-conversation, SellSide Initiation, conversation-ideas-extract, capture-topic, voice-dna, and the IR Scraper. The last one is different: instead of running as a Claude conversation, it executes run.py as a subprocess via run_command and streams the output back to Telegram.1

The Approval Gate in Practice

The part I was most keen to test: Claude pausing mid-workflow and asking me a question on my phone, then resuming when I reply.

The first live test was /scrape SNOW: the IR Scraper for Snowflake. Claude sent three questions before running: document types, date range, and output path. I replied to question 2 with 5 years; from my phone, and the bot sent back: ▶️ Continuing... " Claude resumed with my answer and kept going. The pause-reply-resume pattern worked exactly as intended.

A research query came back first time, no friction. The reply arrived in Telegram inside ten seconds.

Setting up the bot itself took 15 minutes from first line to a working /start response

What I Got Wrong First

My initial plan was a five-day outline covering Twilio, async job queues, iOS Shortcuts, approval gate state machines, and Railway deployment. All of it real:but all of it week-two thinking laid out as day-one requirements.

The useful pushback was: “Why 5 days? Should be simpler.” It was. The working core is 300 lines. Telegram handles polling natively. The skill registry is a dictionary. The agentic loop is a while loop. The complexity I was front-loading was real but unnecessary until the basic thing worked.