Leash
A round orange bulldog with glowing cyan eyes, a spiked collar and a dog tag shaped like a terminal window

Leash

STAY. FETCH. OFF-LEASH.

Claude Code and Codex on Telegram. Always on, parallel, steerable, never rate-limited.

git clone https://github.com/zalogarcia/leash.git ~/leash

MIT licensed. Runs on your own Mac or Linux box. Node 18 or newer, plus Claude Code.

Watch

Ninety seconds. Three things it does.

STAY: your terminal in your pocket. FETCH: hand it the long job. OFF-LEASH: steer it, scale it, never wait.

Thirty second version

01 FETCH

You type. Your machine answers.

Away from your desk. The deploy is half finished, or you just thought of something. You open Telegram and type it. Claude Code runs it on your machine, in your repos, with your config and your sessions.

The progress block is one Telegram message, edited as the run goes. Subagent activity streams in too, indented.

02 THE KIT

Eight things a chat window cannot do.

  • Always on, no open port

    A service under launchd or systemd long polls the Telegram Bot API. Outbound HTTPS only. No tunnel, no webhook, no inbound port, no relay.

  • Parallel background workers

    Long jobs run in their own Claude session. If a worker is busy another spawns, so jobs run beside each other instead of queueing behind each other.

  • Steer a job mid run

    Message while a task is running and it goes into the running task, the same as typing mid turn in the CLI. Change the instruction without killing the work.

  • Reminders and scheduled tasks

    Remind me at 8, or every morning summarize yesterday’s commits. The second one actually runs, as a real Claude task, and reports back.

  • A second engine behind it

    OpenAI Codex is billed separately, so an Anthropic limit does not touch it. Ask it directly, hand it a whole job, or let it pick up queued work while Claude is walled.

  • Your accounts, rotated

    Hold a personal and a work subscription. See each one’s live headroom, swap with one tap, and when the active account is rate limited, background work moves to one that is not.

  • Screenshots, files, voice notes

    Send a screenshot with “why does this look broken?” Images, PDFs and code up to 20 MB land in inbox/ and the caption is the instruction. Voice notes get transcribed and run.

  • Your repos, your sessions

    Ask today, follow up tomorrow, same conversation. Sessions are per lane and per directory, survive restarts and reboots, and your custom slash commands work.

Also in the box: named, resumable chats live progress model switching usage and context remote restart log tail self healing watchdog

03 STAY

Three parts. One of them is yours.

Around two thousand lines of Node that long poll the Telegram Bot API and pipe messages into claude -p. The only network traffic is your machine calling Telegram.

  1. 1

    Your phone

    You type a message, send a screenshot, or hold the mic. Normal Telegram, nothing installed.

  2. 2

    Telegram Bot API

    Your machine long polls it over outbound HTTPS. Telegram never calls in. Every update is checked against your chat ID before anything else happens, so strangers who find your bot get silence.

  3. 3

    Leash, on your machine

    It runs Claude Code and Codex locally, in your repos. Two lanes: a chat lane that is always answerable, and a worker pool that takes the long jobs.

chat lane

One task at a time. Messages sent mid task steer into it, so it is always reachable.

worker pool

bg1, bg2, bg3 and on. Parallel, and never refused: up to three Claude workers run at once and the rest start as slots free. Each worker is a fresh, self contained session, discarded when it drains.

the handback

A finished job reports to the chat session, not to you, framed as untrusted worker data. The assistant decides whether more work is needed, then sends you a short human update. You get a colleague, not a log stream.

No tunnel. No webhook. No cloud relay.

Chat IDs come from Telegram’s servers and cannot be spoofed by a sender.

The bot token lives only in config.json, chmod 600 and gitignored.

Nothing leaves your machine except the Telegram messages themselves, voice note audio if you turn on transcription, and calls to Anthropic’s own OAuth endpoints if you enroll more than one account.

The default permission mode is skip permissions, because a headless run cannot answer a prompt. /yolo off switches to accept edits. Know which one you are running.

It also gives a Telegram chat the ability to run code on your machine. Anyone with access to your logged in Telegram can run commands. Turn on two step verification and read the security section before you install.

The bulldog mid leap with all four legs off the ground and a loose leash trailing behind it

04 OFF-LEASH

Hand it the long job. Keep talking.

A headless run occupies its process until it finishes, so a twenty minute job would normally mean twenty minutes of silence. Long commands, anything you prefix bg:, scheduled tasks and handoffs from a session go to their own worker instead.

The pool never refuses a job. Up to three Claude workers run at once (maxConcurrentWorkers in config.json); a job past that waits in the drop box in arrival order, shows in bg.mjs ps, and starts the moment a worker exits. Codex jobs are not counted. Workers are detached, in their own process group, writing to a file rather than a pipe, so restarting the daemon does not kill them. On the next boot it re-attaches to the ones still running and tells you about the ones that died.

node bg.mjs --file ./brief.md

Pass a real brief as a file, never as a shell argument: backticks inside it would turn into command substitution and the worker would get a brief with those terms silently blanked out.

The bulldog gripping the handle of an orange leash in its teeth and leaning back to tug it

05 THE TUG

Wrong turn? Pull, do not shoot.

A running job used to be unreachable. The only way to change its instructions was to kill it and re-dispatch, which throws away all the context it had already built.

Now a message sent mid task is steered into the running run over the CLI’s streaming input mode. It either folds your note into the current turn or answers it right after. Only when steering is impossible, because nothing is running yet or the run is already finishing, does the message queue instead.

/steer latest skip the browser step, the harness is down

Steer when the brief is still valid and you are adding or correcting an instruction. Kill and re-dispatch only when the brief itself was wrong.

06 THE ROUNDS

Thirty one days. Four of them, replayed.

Not a feature list. This is one month of one person’s real traffic through the bridge, 2026-08-09 to 2026-09-08, counted off the machine that ran it. The four exchanges below actually happened. They are paraphrased and every name in them has been taken out.

  • 1,973

    prompts from the phone

  • 217

    spoken, not typed

  • 228

    screenshots and photos in

  • 614

    jobs handed to a worker

Round one · spoken, then shipped

Spoken at 16:27. The proof came back at 16:43.

Round two · a screenshot is the bug report

Nine minutes from the photo to a committed fix with the tests green.

Round three · correct it, do not kill it

Two corrections into a job that was already running, about ninety seconds apart. Typed plainly and relayed in; /steer aims one at a named worker directly.

Round four · nobody asked for this one

It fires at 08:30 every morning, into the same chat, asked or not.

One day, in order · 2026-09-04

  1. 03:00two scheduled jobs run while the house sleeps
  2. 08:20a voice note, a question about the tool itself
  3. 08:30the daily production sweep fires on its own
  4. 08:52two typed words, and a worker takes it
  5. 10:04a screenshot: this message is a wall of text
  6. 11:11a voice note asks for a launch video
  7. 11:35a second voice note steers it, one minute in
  8. 11:42a worker hits a usage limit, the account rotates
  9. 12:48the recut video reports back with its numbers
  10. 15:17a screenshot: why did it not rotate by itself
  11. 16:27a voice note ships a branch, orders a live check
  12. 16:43the live check reports back green from production

Twelve moments from a day that carried eighty three messages. Local time, one person, one phone.

Counted off the bridge’s own files, not from memory: the transcript each lane writes, the inbox every attachment lands in, and the live schedule. The four replays and the day above come out of those same transcripts.

07 SETUP

Three commands, then talk to it.

Requirements: Claude Code, Node 18 or newer, macOS with launchd or Linux with systemd, and a Telegram account.

  1. 1

    Clone it

    git clone https://github.com/zalogarcia/leash.git ~/leash
    cd ~/leash
  2. 2

    Run the installer

    ./install.sh

    It checks prerequisites, walks you through creating a bot with BotFather, auto detects your chat ID, writes a config.json at chmod 600, installs the service and starts it.

    ./install.sh --dry-run

    Preview exactly what it would do without touching anything.

  3. 3

    Message your bot

    /help

    That is the whole install. Everything after this is optional.

Optional: voice notes

Export an OPENAI_API_KEY in your shell profile, which the bridge reads because service managers do not. Without a key, voice notes are handed to Claude as audio files instead.

Optional, and recommended

Copy the block in templates/CLAUDE.md.example into your own CLAUDE.md. That is what teaches your sessions to hand long jobs to the background lane and to manage your reminders in plain English.

08 QUESTIONS

The ones worth asking.

Is my code sent anywhere?

Nowhere it would not already go when you run Claude Code in your terminal. The bridge adds no destination of its own: your prompts and its replies travel as Telegram messages, voice note audio goes to transcription if you turn that on, and enrolling a second account adds calls to Anthropic’s own OAuth endpoints for the usage numbers. Your repos stay where they are.

Do I need a server, a tunnel or an open port?

None of the three. The bridge long polls the Telegram Bot API over outbound HTTPS, so nothing inbound ever reaches your machine. No webhook, no third party relay. Your machine does need to be awake: messages sent while it sleeps queue at Telegram and are skipped as stale after an hour.

What if a stranger finds my bot?

They get silence. Every incoming update is checked against your chat ID before anything else happens, so there is no reply, no download and no execution for anyone else. Chat IDs come from Telegram’s servers and cannot be spoofed by a sender. The real key is your Telegram account itself, so turn on two step verification and keep an eye on your device list.

Does it work on Linux?

Yes, with systemd. It is tested day to day on macOS with Claude Code 2.x. The Linux path is included and structurally sound but less battle tested, so issues and pull requests are welcome.

What does it cost?

The bridge is MIT licensed and free. You pay for what it drives: your own Claude Code subscription, plus OpenAI usage if you turn on voice note transcription or use Codex as the second engine.

I have two Claude subscriptions. What happens when one is rate limited?

Enroll both. You get each account’s live headroom, one tap swaps, and when the active account is rate limited the bridge rotates new background work to an enrolled account that still has room. Workers already running are never killed by a swap. When every account is walled, newly queued jobs run on Codex instead of waiting, and the reply says so. This is a switcher between subscriptions you already pay for, not a way around anyone’s plan limits.

Why a bulldog?

Because it sits at your feet all day, fetches on command, and looks deeply unimpressed while doing it.