[FEATURE] Allow slash command usage over remote-control

Status Fixed / completed
Maintainer reply None cached
Activity 12 comments · opened Feb 25, 2026 · closed Aug 17, 2026

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_

View original on GitHub ↗

11 Comments

AnonymousScripting · 6 months ago

Please fix this, its a simple issue...

GamerWil26 · 5 months ago

Yes.. /compact and /clear would definitely help for long sessions.. also /context or a way to see current context balance

jptreble2 · 5 months ago

Upvoting this one, I am also interested in /resume

larsencyber · 5 months ago

Definitely agree that this should be added.

sefasenturk95 · 5 months ago

Upvoting this one, I am also interested in /btw 💯

tfvchow · 5 months ago
Note: This was found collaboratively with Claude Code (Opus 4.6). If that bothers you, feel free to scroll past. Nobody's getting paid here — just sharing what I found in case it helps.

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:

// offset 10402791, v2.1.78
q8("tengu_bridge_slash_commands", !1)  // !1 = false

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-jsx type → blocked (these render Ink/React components in the local terminal)
  • prompt type → allowed (these just inject a prompt, no local UI needed)
  • Hardcoded allowlist → allowed (a few local-jsx commands 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 skills

Not sent (~48 commands): /config, /model, /memory, /help, /plan, /resume, /permissions, /remote-control, /usage, etc. — these need the local TUI to render.

Enable

sed -i 's/tengu_bridge_slash_commands",!1/tengu_bridge_slash_commands",!0/g' "$(readlink -f $(which claude))"

npm install -g @anthropic-ai/claude-code will overwrite the patch.

Full write-up: tfvchow/field-notes-public#61

byzabay · 4 months ago

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.

ALenfant · 2 months ago

This needs to be fixed

richardcb · 2 months ago

Still an issue in my remote sessions.

prilly-dev · 2 months ago

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

Ozziki39 · 1 month ago

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.

Showing cached comments. Read the full discussion on GitHub ↗