Per-channel Discord & Telegram sessions for Claude Code — isolated tmux sessions with auto-discovery watchdog, thread context injection, and launchd persistence.
SaferSkills independently audited claude-remote-sessions (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Each Discord channel or thread maps to its own independent Claude Code session running in a tmux pane. Discord messaging is handled natively by the MCP plugin inside each session. A watchdog daemon keeps sessions alive and auto-discovers new channels/threads.
--channels support (v2.1.80+)/discord:configure <token>Read History, Attach Files, Add Reactions, Manage Channels, Create Threads
Create ~/.claude/discord-sessions/config.env:
# Required
DISCORD_ALLOWED_USER_ID="your-discord-user-id"
DISCORD_GUILD_ID="your-guild-server-id"
# Optional (defaults shown)
DISCORD_SESSION_WORKDIR="$HOME" # Default workdir for new sessions
DISCORD_WATCHDOG_INTERVAL=30 # Respawn check frequency (seconds)
DISCORD_DISCOVER_INTERVAL=60 # Channel/thread discovery frequency (seconds)
DISCORD_CLEANUP_INTERVAL=300 # Stale session cleanup frequency (seconds)Find your user ID: Discord Settings → Advanced → Enable Developer Mode → right-click your name → Copy User ID. Find your guild ID: Right-click your server name → Copy Server ID.
Copy scripts to your project:
cp scripts/discord-session-manager.sh ~/your-project/scripts/
cp scripts/discord-watchdog.sh ~/your-project/scripts/
chmod +x ~/your-project/scripts/discord-session-manager.sh
chmod +x ~/your-project/scripts/discord-watchdog.sh# Discover all channels + threads and spawn sessions
./scripts/discord-session-manager.sh discover-all
# Start the watchdog (auto-respawn + auto-discover every 60s)
./scripts/discord-watchdog.sh --daemonScript: scripts/discord-session-manager.sh
# Channel session
./scripts/discord-session-manager.sh spawn <channel_id> --name <label> [--workdir <path>]
# Thread session (fetches last 20 parent messages as context)
./scripts/discord-session-manager.sh spawn-thread <thread_id> <parent_id> [--name <label>] [--workdir <path>]
# Fresh session — resets conversation history for a channel
./scripts/discord-session-manager.sh spawn <channel_id> --name <label> --freshDefault workdir comes from config.env. Override per-session with --workdir to scope a session to a specific project — it loads that project's CLAUDE.md chain automatically.
Each session gets:
dc-<id> running Claude Code with --channels discord--session-id (UUID v5 derived from the channel ID) so conversationhistory persists across watchdog respawns
DISCORD_STATE_DIR with scoped access.json.session-id file in the state directorysessions.json./scripts/discord-session-manager.sh discover # new channels
./scripts/discord-session-manager.sh discover-threads # new threads with parent context
./scripts/discord-session-manager.sh discover-all # bothThe watchdog runs discover-all every 60 seconds. Create a channel or thread on Discord → a session spawns automatically.
./scripts/discord-session-manager.sh create-channel <name>Creates the Discord channel via API AND spawns its session.
./scripts/discord-session-manager.sh cleanup-staleChecks each registered session against the Discord API. Kills and deregisters sessions whose channel has been deleted (HTTP 404) or whose thread has been archived (thread_metadata.archived: true). The watchdog runs this automatically every 5 minutes (configurable via DISCORD_CLEANUP_INTERVAL).
./scripts/discord-session-manager.sh list # UP/DOWN status
./scripts/discord-session-manager.sh status # overview
./scripts/discord-session-manager.sh attach <id> # attach to tmux session
./scripts/discord-session-manager.sh kill <id> # kill and deregister
./scripts/discord-session-manager.sh kill-all # kill everythingScript: scripts/discord-watchdog.sh
./scripts/discord-watchdog.sh --daemon # start in tmux: dc-watchdog
./scripts/discord-watchdog.sh --stop # stop
./scripts/discord-watchdog.sh --status # check if runningEvery 30s: respawns dead sessions. Every 60s: discovers new channels and threads. Every 5m: cleans up stale sessions (deleted channels, archived threads).
See references/launchd.md for a launchd plist template that starts the watchdog on login.
Sessions survive watchdog respawns by using Claude Code's --session-id flag. When a session is spawned, a deterministic UUID v5 is generated from a fixed namespace and the channel/thread ID. This means:
a crash or respawn.
$SESSIONS_DIR/<channel_id>/.session-id so respawn-deadreads it back and passes it to the new claude process.
--fresh when spawning to generate a random UUID v4 instead, resetting theconversation history for that channel.
spawn / spawn-thread calls _generate_session_id(channel_id) which produces adeterministic UUID v5 via python3 -c "uuid.uuid5(namespace, channel_id)".
<state_dir>/.session-id and passed as --session-id <uuid> tothe claude command.
respawn-dead, the persisted UUID is read from .session-idand passed back to _spawn_tmux, so claude resumes the same conversation.
--fresh overrides this with a new random UUID v4, giving the channel a clean slate.When a session receives a message where chat_id differs from its assigned channel, it is a thread message. The session should:
./scripts/discord-session-manager.sh list./scripts/discord-session-manager.sh spawn-thread <chat_id> <channel_id>In practice, the watchdog handles this automatically via discover-threads.
By default, all sessions use the DISCORD_SESSION_WORKDIR from config.env. To assign different project directories to specific channels, create a mapping file:
File: ~/.claude/discord-sessions/workdir-map.json
{
"general": "$HOME/myproject",
"health-os": "$HOME/myproject/apps/healthOS",
"life": "$HOME/myproject/core/life",
"design-system": "$HOME/myproject/apps/arcan-glass"
}How it works:
discover or discover-threads spawns a new session, it looks up the channel/threadname in workdir-map.json.
DISCORD_SESSION_WORKDIR is used.$HOME) are expanded automatically.You can also use --workdir on individual spawn / spawn-thread commands to override the mapping for a single session.
Tip: After updating workdir-map.json, run kill-all then discover-all to re-spawn all sessions with the new workdir assignments. Existing sessions are not affected until they are killed and re-spawned.
The slash command daemon provides Discord-native /command interaction for managing sessions without leaving the chat interface.
The watchdog automatically manages the slash daemon. When the watchdog runs, it checks for the dc-slash-daemon tmux session and spawns it if missing.
# Manual start (standalone)
cd scripts && bun discord-slash-daemon.ts
# Or let the watchdog handle it
./scripts/discord-watchdog.sh --daemon| Command | Description |
|---|---|
/session status | Show session info (workdir, uptime, name, tmux session) as an embed |
/session restart | Kill + respawn the session. Use fresh: true for a clean conversation |
/session refresh | Kill + respawn with the same session-id (picks up new skills/CLAUDE.md) |
/session kill | Kill the session |
/session wake | Wake a suspended session |
/session workdir [path] | Show current workdir, or change it (kills + respawns with new workdir) |
/skills list | List installed skills for the channel's session |
/skills install <name> | Install a skill via npx skills add in the session |
/discover | Trigger channel/thread discovery |
/ask <prompt> | Send a prompt to the channel's Claude session |
The /session workdir command supports autocomplete. It reads entries from ~/.claude/discord-sessions/workdir-map.json and suggests matching paths as the user types.
/skills install <name> sends the install command directly to the tmux session. After installation completes, use /session refresh to kill and respawn the session so it picks up the newly installed skill.
Commands are registered automatically when the daemon starts. To register or update commands without running the full daemon:
bun scripts/register-slash-commands.ts # Register/update commands
bun scripts/register-slash-commands.ts --clear # Remove all guild commandsSLASH_COMMANDS array in discord-slash-daemon.tshandleYourCommand)handleInteractionbun scripts/register-slash-commands.ts to update DiscordThe daemon requires:
bun runtimediscord.js v14 (install: cd scripts && bun install)~/.claude/channels/discord/.env~/.claude/discord-sessions/config.envapplications.commands scope in the guildSymptom: After logging into a new Claude account (claude login), sessions spawn but immediately die. Only the first session survives; all subsequent ones exit silently.
Cause: Stale .session-id files from the previous account. Claude Code's --session-id flag tries to resume a conversation that doesn't exist under the new account, causing the process to exit.
Fix:
# 1. Kill all sessions
./scripts/discord-session-manager.sh kill-all
# 2. Clear stale session IDs
for d in ~/.claude/discord-sessions/*/; do rm -f "$d/.session-id"; done
# 3. Clear session state
echo '{}' > ~/.claude/discord-sessions/sessions.json
# 4. Update OAuth token in config.env (if using CLAUDE_CODE_OAUTH_TOKEN)
# Edit ~/.claude/discord-sessions/config.env with your new token
# 5. Respawn all sessions
./scripts/discord-session-manager.sh discover-allSymptom: Sessions show the Claude Code TUI prompt but no "Listening for channel messages from: plugin:discord" banner.
Cause: The --channels plugin:discord@claude-plugins-official flag is missing from the spawn command. The Discord plugin MCP server only connects when launched with --channels.
Fix: Ensure the session manager includes --channels in the claude command. The flag is required even though the plugin is installed globally.
Symptom: Session shows a rate-limit prompt with options to wait, switch to extra usage, or upgrade.
Fix: Use /session send 2 from Discord to select "Switch to extra usage", or /session send 1 to wait for reset.
Discord #general → tmux: dc-<a> → Claude Code (workdir A, CLAUDE.md chain A)
Discord #project-x → tmux: dc-<b> → Claude Code (workdir B, CLAUDE.md chain B)
Thread: "design" → tmux: dc-<c> → Claude Code (parent context injected)
dc-watchdog → Respawns dead + discovers new + cleans stale
dc-slash-daemon → Handles /session, /skills, /ask, /discover~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.