Originally published at hafiz.dev
You kick off a big database migration on staging, get up to make coffee, and come back 45 minutes later. Claude hit a permission prompt 3 minutes in. Everything's been frozen since then. You had no idea.
That's the specific problem Claude Code Channels fixes.
Shipped on March 20, 2026 as a research preview, Channels is a feature that turns your running Claude Code session into something you can message from anywhere. Fire off a task before a meeting. Check in from your phone. Get a reply in Telegram when it's done. Your laptop stays open, your session stays alive, and you're not chained to the terminal waiting for output.
This isn't a tutorial about what Channels is. If you want that, the official docs cover it fine. This is about what you actually do with it inside a real Laravel project. The setup takes 5 minutes. The workflows take longer to figure out on your own, so I'm going to save you the trial and error.
What Channels Actually Is (The Short Version)
A Channel is an MCP server that runs locally alongside your Claude Code session. It acts as a bridge between an external messaging platform and your active terminal session.
The key word is "local." Your code never leaves your machine. When you send a message from Telegram, the plugin (running on your laptop) receives it and injects it directly into your open Claude Code session as an event. Claude processes it with full access to your filesystem, your MCP servers, your git history, everything. Then it replies back through the same channel.
Claude Code
Channels Message Flow
Your
Phone
DM
Bot
API
polls
MCP
Plugin
event
Claude
Session
reads
Your
Codebase
function alInit(){
if(!document.getElementById('al-s0')){setTimeout(alInit,50);return;}
var AMBER='#c9aa71',GREEN='#4ade80',STEP=520,CONN=420,HOLD=900,PAUSE=600;
var msgs=['message traveling from phone \u2192','routing through Telegram Bot API \u2192','MCP plugin injecting event into session \u2192','Claude working in your local project \u2192','codebase access: full \u2014 composing reply...','reply delivered back through Telegram \u2713'];
function nd(i){return document.getElementById('al-s'+i);}
function ic(i){return document.getElementById('al-i'+i);}
function lb(i){return document.getElementById('al-l'+i);}
function cn(i){return document.getElementById('al-c'+i);}
function aw(i){return document.getElementById('al-a'+i);}
function ar(i){return document.getElementById('al-arr'+i);}
var st=document.getElementById('al-status');
function setNode(i,color){nd(i).style.borderColor=color;nd(i).style.background=color===AMBER?'rgba(201,170,113,0.12)':'rgba(74,222,128,0.12)';ic(i).style.color=color;lb(i).style.color=color===AMBER?'rgba(201,170,113,0.9)':'rgba(74,222,128,0.9)';}
function fillConn(i,color,cb){
var c=cn(i),a=aw(i),arrEl=ar(i);
var arrowColor=color===AMBER?'rgba(201,170,113,0.7)':'rgba(74,222,128,0.7)';
if(arrEl){arrEl.querySelector('polygon').setAttribute('fill',color);arrEl.style.opacity='0';}
c.style.background=color;c.style.transition='none';c.style.width='0%';
a.style.color=arrowColor;
requestAnimationFrame(function(){requestAnimationFrame(function(){
c.style.transition='width '+CONN+'ms ease';c.style.width='100%';
setTimeout(function(){if(arrEl)arrEl.style.opacity='1';cb();},CONN+40);
});});
}
function resetAll(){
for(var i=0;i<5;i++){nd(i).style.borderColor='rgba(201,170,113,0.2)';nd(i).style.background='#1a1a26';ic(i).style.color='rgba(201,170,113,0.25)';lb(i).style.color='rgba(224,224,224,0.3)';}
for(var j=0;j<4;j++){var c=cn(j);c.style.transition='none';c.style.width='0%';aw(j).style.color='rgba(201,170,113,0.0)';var arrEl=ar(j);if(arrEl)arrEl.style.opacity='0';}
st.style.opacity='0';
}
function setStatus(msg){st.style.opacity='0';setTimeout(function(){st.textContent=msg;st.style.opacity='1';},150);}
function loop(){
resetAll();
setTimeout(function(){
setNode(0,AMBER);setStatus(msgs[0]);
setTimeout(function(){fillConn(0,AMBER,function(){
setNode(1,AMBER);setStatus(msgs[1]);
setTimeout(function(){fillConn(1,AMBER,function(){
setNode(2,AMBER);setStatus(msgs[2]);
setTimeout(function(){fillConn(2,AMBER,function(){
setNode(3,AMBER);setStatus(msgs[3]);
setTimeout(function(){fillConn(3,AMBER,function(){
setNode(4,AMBER);setStatus(msgs[4]);
setTimeout(function(){
setStatus(msgs[5]);
for(var k=0;k<5;k++){setNode(k,GREEN);}
setTimeout(function(){setTimeout(loop,PAUSE);},HOLD);
},STEP);
});},STEP);
});},STEP);
});},STEP);
});},STEP);
},400);
}
loop();
}
alInit();
One thing the docs are clear about: events only arrive while your session is open. Close the terminal and messages sent during that time are gone. They won't queue up and deliver later. So for anything you want to monitor while you're away, you need to keep Claude Code running in a tmux or screen session. More on this in a minute.
Getting Set Up (The Minimum You Need to Know)
Before you start: Channels requires Claude Code v2.1.80 or later, a claude.ai login (API key auth doesn't work), and the Bun runtime. Not Node. Bun. This trips up a lot of people. If you install the plugin and nothing happens, Bun is almost certainly the reason.
# Check your Claude Code version first
claude --version
# Install Bun if you don't have it
curl -fsSL https://bun.sh/install | bash
Once you're past that, the Telegram setup is three commands inside an active Claude Code session:
# Install the official plugin
/plugin install telegram@claude-plugins-official
# Save your BotFather token
/telegram:configure YOUR_BOT_TOKEN_HERE
Then exit and relaunch with the channels flag:
claude --channels plugin:telegram@claude-plugins-official
DM your bot on Telegram. It replies with a 6-character pairing code. Go back to your terminal:
/telegram:access pair YOUR_PAIRING_CODE
Done. You're connected. Send a message from Telegram and Claude responds in the chat.
One more thing to do immediately: lock down access.
/telegram:access policy allowlist
By default, anyone who messages your bot gets a pairing code reply. That's fine for testing, but you don't want strangers trying to pair with your session. Run the allowlist command before you forget.
Now. Let's talk about what you actually use this for.
Scenario 1: Long-Running Artisan Commands
The migration scenario from the intro is the most common pain point. You're running something that takes time and needs a yes/no at some point in the middle. A data import. A multi-step migration. A batch reprocessing job on a large table.
With Channels, the workflow looks like this. You kick off the command and tell Claude what to expect:
You: Run the import:customers artisan command against the staging database.
It'll ask for confirmation before it starts writing. Wait for my approval
before you proceed.
Then you walk away. When Claude hits the confirmation step, it messages you in Telegram:
Claude: The import is ready to begin. It found 14,832 customer records
to process. The command is asking for confirmation before writing.
Should I proceed? (yes/no)
You reply "yes" from your phone. Claude approves the prompt in the terminal and the import runs.
This works for any scenario where you want a human checkpoint in the middle of a longer task. Migrations against production data. Seed scripts that drop and recreate tables. Any --force command you want to consciously approve rather than just automate past.
Scenario 2: Queue Failures While You're Not at Your Desk
If you're running a Laravel app with queued jobs, you already know the late-night failed job routine. Horizon shows something red. You get an email. You sigh and open the laptop.
Laravel Queue Jobs: Processing 10,000 Tasks Without Breaking covers the architecture side well. Channels adds the response layer.
Keep Claude Code running in a tmux session on your development machine with your project loaded:
tmux new-session -d -s claude-session
tmux send-keys -t claude-session \
'cd ~/clients/myapp && claude --channels plugin:telegram@claude-plugins-official --dangerously-skip-permissions' Enter
Now from your phone, whenever something looks off:
You: Check the failed jobs in Horizon. What's failing and why?
Claude reads the failed job records, checks the exception messages, looks at the relevant code, and replies:
Claude: 3 jobs failed in the last hour. All are ProcessInvoicePdf jobs.
The exception is: Unable to find class App\Services\PdfRenderer.
Looks like a namespace change that wasn't reflected in the job class.
The fix is a one-line update in ProcessInvoicePdf.php line 23.
Want me to fix it and restart the queue?
You reply "yes, fix it" from Telegram. Claude makes the change and tells you when it's done. No laptop needed.
The --dangerously-skip-permissions flag is doing real work here. Without it, every file write would pause and wait for terminal confirmation, which defeats the purpose when you're trying to resolve something remotely. I'll be honest about the trade-off: you're giving up the permission safety net. Only use this on machines and projects you trust, in environments where the blast radius of a bad action is acceptable.
Scenario 3: Client Bug Reports When You're Away from the Desk
This one happens constantly in client work. Someone messages you about a bug at 7pm. You're not at your computer. Before Channels, your options were: ignore it until morning, or open the laptop.
One thing to understand clearly before you rely on this scenario: three conditions must all be true at the same time. Your machine has to be on. Claude Code has to be running with --channels in a tmux session. And the right client project has to be loaded in that session. If you have two active clients, you need two named tmux sessions:
tmux new-session -s client-a # client A project loaded here
tmux new-session -s client-b # client B project loaded here
Each session runs Claude Code pointed at the right directory. When a bug comes in for client 1, you're messaging into the session that already has their codebase loaded. This is the setup that makes Scenario 3 actually work in practice.
With that in place, when a client reports a bug you forward it to Claude in Telegram:
You: Client just reported that the invoice filter on the dashboard isn't
working. When they select "overdue" from the dropdown, it shows all
invoices instead of filtering. Look at InvoiceController and the
related Vue component and tell me what's wrong.
Claude reads both files, traces the issue, and comes back to you in the chat:
Claude: Found it. In InvoiceController@index, the 'overdue' case in the
switch statement is missing a break, so it falls through to the default
'all' query. The fix is adding `break;` on line 67. The Vue component
is fine. Want me to write the fix and a test for it?
You reply "yes, write the fix and test, but don't commit yet." Claude writes both. In the morning you sit down, review what it wrote, and either commit or adjust. The diagnosis work is already done. Half the problem is solved before you've touched the keyboard.
This is where Channels starts to feel less like a tool and more like having a developer you can text. You're not directing every step. You're checking in, approving things, and staying informed without being present.
Scenario 4: Kicking Off Feature Work Before You Step Away
This one pairs well with the Ralph Wiggum Technique and git worktrees approach.
Start a well-scoped task before you leave for lunch or a meeting. Give Claude a clear spec via Telegram, with the project already loaded in the session:
You: Build the notification preferences page. Users should be able to toggle
email notifications on/off for: new invoice, payment received, invoice
overdue. Save preferences to user_notification_preferences table (create
the migration). Vue component in resources/js/Pages/Settings/Notifications.vue.
Use the existing NotificationService. Don't build the actual email sending,
just the preferences UI and storage.
Claude starts working. The Telegram typing indicator shows it's active. When it finishes (or hits something it needs to ask about), it messages you:
Claude: Done. Here's what I built:
- Migration: create_user_notification_preferences_table
- Model: UserNotificationPreference with fillable and relationship on User
- Controller: NotificationPreferenceController (store/update)
- Route: PATCH /settings/notifications
- Vue component: Notifications.vue with three toggles, auto-saves on change
- I didn't find NotificationService in your app, so I left a TODO comment
where it should be called. Can you point me to the right file?
You clarify. It continues. Back and forth over Telegram, no laptop open.
The MCP server setup matters a lot here. The more context Claude has about your project conventions and codebase, the less you have to explain in each Telegram message. Get your MCP configuration right first and these remote sessions become much more productive.
The Session-Open Requirement and tmux
One thing to understand clearly: Channels is not a cloud service. Claude Code isn't running somewhere persistent on Anthropic's servers waiting for your messages. It's running on your machine. Close the terminal, and it's gone.
For workflows where you want to kick something off and check in later, you need to keep the session alive. tmux is the standard solution:
# Start a persistent session
tmux new-session -s claude
# Later, reattach from any terminal
tmux attach -t claude
# Or check what's running
tmux ls
Start Claude Code inside the tmux session with the channels flag, and it'll keep running even if you close your terminal window. The session persists until your machine restarts or you explicitly kill it.
One thing that still requires the terminal: permission prompts. If you're not using --dangerously-skip-permissions and Claude hits a prompt it needs answered, the session pauses. You can't approve it from Telegram. You have to get back to the terminal. This is a known limitation during the research preview, and based on the docs it sounds like remote permission approval is on the roadmap.
What Channels Isn't
Worth being honest about the current state. This is a research preview. A few things that don't work yet or have rough edges:
No message history. The Telegram Bot API doesn't expose message history. If Claude replies while your phone has no signal, the message is gone. You won't see it when you reconnect.
Only Anthropic-whitelisted plugins for now. During the preview, --channels only accepts plugins from claude-plugins-official. If you want to build a custom Slack channel or a webhook bridge for something like Forge deploy notifications, you need the --dangerously-load-development-channels flag, which disables the allowlist entirely.
Availability is still rolling out. Some accounts don't have it yet even after updating. If the --channels flag doesn't register anything on startup, you may just not have access yet.
If you were using OpenClaw on Forge before, Channels covers most of the same ground with better first-party support. OpenClaw still has an edge on platform breadth (iMessage, WhatsApp, Slack), but for Telegram and Discord use cases, Channels is the cleaner option.
FAQ
Do I need a special Claude plan to use Channels?
You need a claude.ai login. It works on Pro, Max, Team, and Enterprise plans. Console and API key authentication aren't supported. Team and Enterprise orgs also need an admin to enable it in managed settings before individual setup works.
Can I use Discord instead of Telegram?
Yes. The setup is slightly more involved (you need to create a bot in the Discord Developer Portal and enable Message Content Intent under Privileged Gateway Intents), but the workflow is the same. Run /plugin install discord@claude-plugins-official and /discord:configure YOUR_TOKEN in your Claude Code session, then relaunch with --channels plugin:discord@claude-plugins-official.
Is it safe to use --dangerously-skip-permissions?
It depends entirely on your environment. On a local dev machine with a project you fully control, it's fine. On a machine with production database credentials and live API keys, think carefully. The flag removes all permission checkpoints. If Claude misinterprets an instruction, it'll execute without asking. Use it where the blast radius of a mistake is acceptable.
What happens to messages I send when Claude Code isn't running?
They're lost. The channel plugin runs locally and only polls while Claude Code is active. If the session is closed, no one's listening. This is why tmux matters for always-on setups.
Can I run multiple channel plugins at the same time?
Yes. Space-separate them in the --channels flag: claude --channels plugin:telegram@claude-plugins-official plugin:discord@claude-plugins-official. Both channels will be active in the same session.
The Shift That Actually Matters
The practical change here is smaller than the hype suggests, but it's real. You go from "I have to be at my terminal to do anything with Claude" to "I can start something, step away, and stay in the loop from wherever I am."
For client work especially, that's meaningful. A bug comes in at 7pm. You don't have to choose between ignoring it and sitting back down at your desk. You fire off the investigation from your phone and handle it on your own schedule.
Set up tmux, get the Bun requirement sorted first, and lock down your allowlist before you share your bot token anywhere. The rest is just workflow.
Top comments (0)