-
Notifications
You must be signed in to change notification settings - Fork 0
Session Lifecycle
The SessionManager class manages the full lifecycle of tmux sessions: creation, environment injection, Claude startup, prompt delivery, and cleanup.
┌──────────┐
│ No │
/new or │ Session │
first msg │ │
└────┬─────┘
│ ensure()
▼
┌──────────┐
│ Creating │ tmux new-session
└────┬─────┘
│
▼
┌──────────┐
│ Injecting│ exportEnvToSession()
│ Env │ DISCORD_TOKEN + DEFAULT_CHANNEL_ID
└────┬─────┘
│
▼
┌──────────┐
│ Starting │ claude --append-system-prompt [--continue]
│ Claude │
└────┬─────┘
│
▼
┌──────────┐
│ Waiting │ poll for ❯ prompt (60s timeout)
│ Ready │
└────┬─────┘
│
▼
┌──────────┐ /enter
│ Active │◄──────── send prompts
│ │ /reset
└────┬─────┘──────────► kill + clear state
│
│ /delete
▼
┌──────────┐
│ Destroyed│ tmux kill-session + DB cleanup
└──────────┘
ensure(channelId, initialText?) is the primary method for getting a session ready:
-
Check cache — if
SessionStateexists in the in-memory Map, return it - Load from DB — find the Room record, determine workspace dir and tmux session name
-
Check tmux —
tmux has-sessionto see if already running -
If not running:
- Check resume signals (see below)
- Create tmux session:
tmux new-session -d -s <name> -x 200 -y 50 - Set working directory:
cd <workspace> - Export environment variables
- Start Claude Code with appropriate flags
- Wait for
❯prompt - Write
.claude-tmux-discord/startedmarker
- Queue initial text if provided
Two independent signals trigger --continue (resume previous conversation):
-
Bot marker file:
<workspace>/.claude-tmux-discord/started— written by the bot after first successful startup -
Claude history:
~/.claude/projects/<encoded-abs-path>/*.jsonl— Claude's own conversation log files
If either signal exists → Claude starts with --continue.
The workspace path is encoded for the Claude projects directory by replacing / with - and prepending the drive letter equivalent.
exportEnvToSession() uses a dual strategy:
tmux set-environment -t <session> DISCORD_TOKEN <value>
tmux set-environment -t <session> DEFAULT_CHANNEL_ID <channelId>
Plus literal keystrokes:
export DISCORD_TOKEN='<value>' DEFAULT_CHANNEL_ID='<channelId>'
The 500ms delay between set-environment and export keystrokes ensures both methods take effect. Both are needed because:
-
set-environmentonly affects new child processes -
exportkeystrokes affect the current shell - Claude's CWD may differ from where
.envlives
Each SessionState has a busy property — a Promise<void> chain. Operations are enqueued via:
state.busy = state.busy.then(() => doWork());This serializes all tmux input for a channel, preventing:
- Concurrent prompts corrupting each other
- Race conditions between
/enterand button clicks - Double-sends from rapid user interaction
Polls capturePane() every 1 second for up to 60 seconds, looking for the ❯ character on the last non-empty line. If it times out, it proceeds anyway with a warning log — the serial promise chain ensures correctness even if the check fails.
-
/reset: Kills tmux session, removesSessionStatefrom Map. Next/entercreates fresh. -
/delete: Kills tmux session, removes DB row, optionally wipes workspace, deletes Discord channel. - Bot shutdown (SIGINT/SIGTERM): Kills all active tmux sessions gracefully.
-
resumeAll(): On startup, iterates all registered rooms and callsensure()to reconnect.
claude-tmux-discord — Discord bot for per-channel Claude Code sessions via tmux
Setup
Usage
Technical
Help