[FEATURE] Allow slash command usage over remote-control
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
While the remote-control feature is very nice and much appreciated, I think allowing the use of slash commands through the remote-control connection would be a great improvement. They don't seem to work, currently.
Proposed Solution
I'd like to be able to use commands such as /clear, /compact, etc. over the remote-control connection.
Alternative Solutions
As an alternative, I've previously used Happy to control claude sessions remotely with some success but having a solution directly from anthropic is preferable.
Priority
Medium - Would be very helpful
Feature Category
CLI commands and flags
Use Case Example
Let's say i'm working on a project and want to change the model, or compact at a certain time, or clear the context. Being able to use a slash command like we can in the CLI would be nice.
Additional Context
_No response_
Showing cached comments. Read the full discussion on GitHub ↗
11 Comments
Please fix this, its a simple issue...
Yes.. /compact and /clear would definitely help for long sessions.. also /context or a way to see current context balance
Upvoting this one, I am also interested in /resume
Definitely agree that this should be added.
Upvoting this one, I am also interested in /btw 💯
The slash command picker already works — it's feature-flagged off
The slash command picker already works on Claude Desktop and claude.ai/code web in non-Remote-Control sessions, so the client-side rendering is ready. The issue is on the CLI side — the code to send the command list over the bridge exists but is behind a feature flag that defaults to off:
When enabled, the CLI filters commands to those that are remote-compatible, sends the list over the bridge, and the remote client renders a working picker. Tested on claude.ai/code web and Claude Desktop — both render the picker and commands execute correctly.
What's remote-compatible
The filter function (
nh1, offset 10402791) decides what gets sent:local-jsxtype → blocked (these render Ink/React components in the local terminal)prompttype → allowed (these just inject a prompt, no local UI needed)local-jsxcommands explicitly permitted)In practice:
Sent to remote:
/commit,/commit-push-pr,/init,/init-verifiers,/insights,/compact,/clear,/cost,/files,/release-notes, plus all user custom commands and skillsNot sent (~48 commands):
/config,/model,/memory,/help,/plan,/resume,/permissions,/remote-control,/usage, etc. — these need the local TUI to render.Enable
npm install -g @anthropic-ai/claude-codewill overwrite the patch.Full write-up: tfvchow/field-notes-public#61
Please, please fix. It's very silly that everything works remotely in Discord except actually CLI calls, which means if you're away from your computer and you need to enter a CLI command or you run out of usage, you number one can't see that you've run out of usage and number two you can't enable the ability to use additional credits or to wait. If you're away from your computer for hours, this is a truly devastating feeling.
This needs to be fixed
Still an issue in my remote sessions.
i see there has been a regression, it was working up until a couple of days ago. its not possible to run /compact and some other commands anymore
Hey guys this is the fix I come up with. give it to your CC and it can do the rest for you. ALWAYS VALIDATE BEFORE DOING ANYTHING
I hope this helps. feel free to reach out with any questions.
This is the exact gap in the issue: we run several long-lived Claude Code sessions on a server and needed to trigger /compact, /clear, /context, and custom skills remotely from Discord while away from the terminal.
Hooks can't do this. A UserPromptSubmit hook can detect a /compact request but can't make the CLI execute it — the CLI only processes slash commands typed into its own input box. So the workaround has to actually put the keystrokes into that box.
What works: a tiny sidecar bot that types the command into the session's terminal for you. Each Claude Code session runs inside a named tmux session, so a separate process can inject keystrokes into that pane exactly as if the operator typed them. The CLI then processes the slash command natively. This runs alongside the Channels plugin, not through it.
The four moving parts
Each CC session runs inside a named tmux session — tmux new-session -s myagent 'claude ...'. That name is the address you inject to.
A separate Discord bot registers native slash commands (discord.py app_commands). It does not share the CC process — it's a sidecar.
On a command it injects into the tmux pane using a two-send pattern (the one non-obvious gotcha, below).
For long commands (/compact, multi-minute skills) it polls the pane for a completion marker, then posts the pane output back to the channel.
The one gotcha: the two-send submit pattern
A single tmux send-keys "…" Enter will sometimes leave your text sitting unsubmitted in the CLI's input composer. The reliable fix is to send the payload, pause, then send a second bare Enter as a "submit nudge":
def inject(session, text):
subprocess.run(["tmux", "send-keys", "-t", session, text, "Enter"])
time.sleep(0.5)
subprocess.run(["tmux", "send-keys", "-t", session, " ", "Enter"]) # submit nudge
Without the second send, remote commands intermittently appear to "do nothing." This was our single biggest source of flakiness.
Completion detection for long commands
/compact and skills take seconds to minutes. Poll the pane for a marker string from the CLI's own output, then return the tail:
def tail(session, n=40):
r = subprocess.run(["tmux", "capture-pane", "-t", session, "-p"],
capture_output=True, text=True)
return "\n".join(r.stdout.rstrip().splitlines()[-n:])
after inject(): poll capture-pane every ~10s until a marker like "Compacted"
appears, up to a max wait, then channel.send(tail(session)).
Two practical notes:
Discord interaction tokens expire at ~15 min. Post long-running results with a normal channel.send(), not an interaction followup — otherwise the reply fails on anything slow.
Pick markers from the CLI's own output: /compact prints "Compacted…"; custom skills can end on a sentinel line you define.
Access control — do this, it's keystroke injection
This types into a live terminal, so gate it hard:
Allow only specific Discord user IDs.
Keep an exclusion set of sessions that must never be injected, any session whose input box may hold unsent text, because your injection appends to whatever is already in the composer. (Learned the hard way: injecting into a session with a half-typed, unsubmitted message submitted a garbled combined command.)
Register commands to your own guild only, not globally.
Minimal reference implementation
import subprocess, time, asyncio
import discord
from discord import app_commands
ALLOWED_USER = 000000000000000000 # your Discord user id
GUILD_ID = 000000000000000000 # your server id
EXCLUDED = {"session-that-may-hold-unsent-input"}
def sessions():
r = subprocess.run(["tmux","list-sessions","-F","#{session_name}"], capture_output=True, text=True)
return r.stdout.split()
def resolve(name): # case-insensitive match to a live session
return next((s for s in sessions() if s.lower() == name.strip().lower()), None)
def inject(session, text):
subprocess.run(["tmux","send-keys","-t",session,text,"Enter"])
time.sleep(0.5)
subprocess.run(["tmux","send-keys","-t",session," ","Enter"]) # submit nudge
def tail(session, n=40):
r = subprocess.run(["tmux","capture-pane","-t",session,"-p"], capture_output=True, text=True)
return "\n".join(r.stdout.rstrip().splitlines()[-n:])
class Bridge(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
g = discord.Object(id=GUILD_ID)
self.tree.copy_global_to(guild=g)
await self.tree.sync(guild=g)
bot = Bridge()
async def gate(inter, agent):
if inter.user.id != ALLOWED_USER:
await inter.response.send_message("not authorized", ephemeral=True); return None
if agent.strip().lower() in EXCLUDED:
await inter.response.send_message("session excluded", ephemeral=True); return None
s = resolve(agent)
if not s:
await inter.response.send_message(f"no session
{agent}; live: {', '.join(sessions())}", ephemeral=True)return s
async def run(inter, session, command, first_wait, markers=None, max_wait=0):
await inter.response.defer()
await asyncio.to_thread(inject, session, command)
await inter.followup.send(f"
{command}-> {session}")waited = first_wait; await asyncio.sleep(first_wait); done = not markers
while markers and max_wait and waited < max_wait:
if any(m in await asyncio.to_thread(tail, session, 60) for m in markers):
done = True; break
await asyncio.sleep(10); waited += 10
# channel.send (NOT followup) — survives the 15-min interaction-token limit
out = (await asyncio.to_thread(tail, session))[:1800]
await inter.channel.send(f"{session} ({'done' if done else 'still running'})\n``
\n{out}\n``")@bot.tree.command(description="Run /compact in a CC session")
async def compact(inter: discord.Interaction, agent: str):
s = await gate(inter, agent)
if s: await run(inter, s, "/compact", first_wait=15, markers=["Compacted","compacted"], max_wait=900)
@bot.tree.command(description="Run /clear in a CC session")
async def clear(inter: discord.Interaction, agent: str):
s = await gate(inter, agent)
if s: await run(inter, s, "/clear", first_wait=5)
@bot.tree.command(description="Run /cost in a CC session")
async def cost(inter: discord.Interaction, agent: str):
s = await gate(inter, agent)
if s: await run(inter, s, "/cost", first_wait=5) # scrapes the usage summary back
bot.run("YOUR_BOT_TOKEN") # load from env/file — do not hardcode
Run it as a small systemd service next to your sessions.
Honest caveats / limitations
Requires your CC sessions to run inside tmux (or screen with equivalent send-keys) on a host you control. Not a fit for the hosted/mobile Remote Control surface.
It's keystroke injection, not a real API — output is scraped from the pane, so it's best-effort text, not structured data.
The same approach fires custom skills and any slash command, not just built-ins — you're just typing into the CLI.
Telegram works identically — swap the Discord bot for a Telegram bot; the tmux half is unchanged.
Native support (what this issue requests) would be cleaner and safer than pane-scraping. Until then, this sidecar has been reliable in daily use.