Skip to content

Session Lifecycle

Rina edited this page May 12, 2026 · 1 revision

Session Lifecycle

Overview

The SessionManager class manages the full lifecycle of tmux sessions: creation, environment injection, Claude startup, prompt delivery, and cleanup.

Session States

                    ┌──────────┐
                    │  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() — The Entry Point

ensure(channelId, initialText?) is the primary method for getting a session ready:

  1. Check cache — if SessionState exists in the in-memory Map, return it
  2. Load from DB — find the Room record, determine workspace dir and tmux session name
  3. Check tmuxtmux has-session to see if already running
  4. 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/started marker
  5. Queue initial text if provided

Resume Detection

Two independent signals trigger --continue (resume previous conversation):

  1. Bot marker file: <workspace>/.claude-tmux-discord/started — written by the bot after first successful startup
  2. 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.

Environment Injection

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-environment only affects new child processes
  • export keystrokes affect the current shell
  • Claude's CWD may differ from where .env lives

Serial Promise Chain

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 /enter and button clicks
  • Double-sends from rapid user interaction

waitForClaudeReady()

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.

Cleanup

  • /reset: Kills tmux session, removes SessionState from Map. Next /enter creates 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 calls ensure() to reconnect.

Clone this wiki locally