commit 72ca6352731a6a91d6a08be2cdaa10dc12881bd2 Author: Zhongwei Li Date: Sat Nov 29 18:17:58 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..27f48db --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,11 @@ +{ + "name": "tmux", + "description": "Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output. Use when debugging applications, running interactive REPLs (Python, gdb, ipdb, psql, mysql, node), automating terminal workflows, or when user mentions tmux, debugging, or interactive shells.", + "version": "1.4.0", + "author": { + "name": "Alberto Leal" + }, + "skills": [ + "./" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d885dbe --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tmux + +Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output. Use when debugging applications, running interactive REPLs (Python, gdb, ipdb, psql, mysql, node), automating terminal workflows, or when user mentions tmux, debugging, or interactive shells. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..a89e1af --- /dev/null +++ b/SKILL.md @@ -0,0 +1,622 @@ +--- +name: tmux +description: "Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output. Use when debugging applications, running interactive REPLs (Python, gdb, ipdb, psql, mysql, node), automating terminal workflows, or when user mentions tmux, debugging, or interactive shells." +license: Vibecoded +--- + +# tmux Skill + +Use tmux as a programmable terminal multiplexer for interactive work. Works on Linux and macOS with stock tmux; avoid custom config by using a private socket. + +## Quickstart + +The session registry eliminates repetitive socket/target specification through automatic session tracking (~80% reduction in boilerplate): + +**IMPORTANT**: Before creating a new session, ALWAYS check existing sessions first to avoid name conflicts: + +```bash +# Check existing sessions to ensure name is available +./tools/list-sessions.sh + +# Create and register a Python REPL session (choose a unique name) +./tools/create-session.sh -n claude-python --python + +# Send commands using session name (auto-lookup socket/target) +./tools/safe-send.sh -s claude-python -c "print(2+2)" -w ">>>" + +# Or with a single session, omit -s entirely (auto-detect) +./tools/safe-send.sh -c "print('hello world')" -w ">>>" + +# List all registered sessions with health status +./tools/list-sessions.sh + +# Clean up dead sessions +./tools/cleanup-sessions.sh +``` + +After starting a session, ALWAYS tell the user how to monitor it by giving them a command to copy/paste (substitute actual values from the session you created): + +``` +To monitor this session yourself: + ./tools/list-sessions.sh + +Or attach directly: + tmux -S attach -t + +Or to capture the output once: + tmux -S capture-pane -p -J -t :0.0 -S -200 +``` + +This must ALWAYS be printed right after a session was started (i.e. right before you start using the session) and once again at the end of the tool loop. But the earlier you send it, the happier the user will be. + +## How It Works + +The session registry provides three ways to reference sessions: + +1. **By name** using `-s session-name` (looks up socket/target in registry) +2. **Auto-detect** when only one session exists (omit `-s`) +3. **Explicit** using `-S socket -t target` (backward compatible) + +Tools automatically choose the right session using this priority order: +1. Explicit `-S` and `-t` flags (highest priority) +2. Session name `-s` flag (registry lookup) +3. Auto-detect single session (if only one exists) + +**Benefits:** +- No more repeating `-S socket -t target` on every command +- Automatic session discovery +- Built-in health tracking +- Activity timestamps for cleanup decisions +- Fully backward compatible + +## Common Workflows + +For practical examples of managing tmux sessions through their lifecycle, see the [Session Lifecycle Guide](./references/session-lifecycle.md). + +This guide covers: +- **Daily workflows**: Ephemeral sessions, long-running analysis, crash recovery, multi-session workspaces +- **Decision trees**: Create vs reuse, cleanup timing, error handling +- **Tool reference matrix**: Which tools to use at each lifecycle stage +- **Troubleshooting**: Quick fixes for common problems (session not found, commands not executing, cleanup issues) +- **Best practices**: 10 DO's and 10 DON'Ts with examples + +## Finding sessions + +List all registered sessions with health status: +```bash +./tools/list-sessions.sh # Table format +./tools/list-sessions.sh --json # JSON format +``` + +Output shows session name, socket, target, health status, PID, and creation time. + +## Sending input safely + +The `./tools/safe-send.sh` helper provides automatic retries, readiness checks, and optional prompt waiting: + +```bash +# Using session name (looks up socket/target from registry) +./tools/safe-send.sh -s claude-python -c "print('hello')" -w ">>>" + +# Auto-detect single session (omit -s) +./tools/safe-send.sh -c "print('world')" -w ">>>" + +# Explicit socket/target (backward compatible) +./tools/safe-send.sh -S "$SOCKET" -t "$SESSION":0.0 -c "print('hello')" -w ">>>" +``` + +See the [Helper: safe-send.sh](#helper-safe-sendsh) section below for full documentation. + +## Watching output + +- Capture recent history (joined lines to avoid wrapping artifacts): `tmux -S "$SOCKET" capture-pane -p -J -t target -S -200`. +- For continuous monitoring, poll with the helper script (below) instead of `tmux wait-for` (which does not watch pane output). +- You can also temporarily attach to observe: `tmux -S "$SOCKET" attach -t "$SESSION"`; detach with `Ctrl+b d`. +- When giving instructions to a user, **explicitly print a copy/paste monitor command** alongside the action—don't assume they remembered the command. + +## Spawning Processes + +Some special rules for processes: + +- when asked to debug, use lldb by default +- **CRITICAL**: When starting a Python interactive shell, **always** set the `PYTHON_BASIC_REPL=1` environment variable before launching Python. This is **essential** - the non-basic console (fancy REPL with syntax highlighting) interferes with send-keys and will cause commands to fail silently. + ```bash + # When using create-session.sh, this is automatic with --python flag + ./tools/create-session.sh -n my-python --python + + # When creating manually: + tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter + ``` + +## Synchronizing / waiting for prompts + +Use timed polling to avoid races with interactive tools: + +```bash +# Wait for Python prompt +./tools/wait-for-text.sh -s claude-python -p '^>>>' -T 15 -l 4000 + +# Auto-detect single session +./tools/wait-for-text.sh -p '^>>>' -T 15 + +# Explicit socket/target +./tools/wait-for-text.sh -S "$SOCKET" -t "$SESSION":0.0 -p '^>>>' -T 15 -l 4000 +``` + +For long-running commands, poll for completion text (`"Type quit to exit"`, `"Program exited"`, etc.) before proceeding. + +## Interactive tool recipes + +- **Python REPL**: Use `./tools/create-session.sh -n my-python --python`; wait for `^>>>`; send code; interrupt with `C-c`. The `--python` flag automatically sets `PYTHON_BASIC_REPL=1`. +- **gdb**: Use `./tools/create-session.sh -n my-gdb --gdb`; disable paging with safe-send; break with `C-c`; issue `bt`, `info locals`, etc.; exit via `quit` then confirm `y`. +- **Other TTY apps** (ipdb, psql, mysql, node, bash): Use `./tools/create-session.sh -n my-session --shell`; poll for prompt; send literal text and Enter. + +## Cleanup + +Killing sessions (recommended - removes both tmux session and registry entry): +```bash +# Kill a specific session by name +./tools/kill-session.sh -s session-name + +# Auto-detect and kill single session +./tools/kill-session.sh + +# Dry-run to see what would be killed +./tools/kill-session.sh -s session-name --dry-run +``` + +Registry cleanup (removes registry entries only, doesn't kill tmux sessions): +```bash +# Remove dead sessions from registry +./tools/cleanup-sessions.sh + +# Remove sessions older than 1 hour +./tools/cleanup-sessions.sh --older-than 1h + +# See what would be removed (dry-run) +./tools/cleanup-sessions.sh --dry-run +``` + +Manual cleanup (when not using registry): +- Kill a session when done: `tmux -S "$SOCKET" kill-session -t "$SESSION"`. +- Kill all sessions on a socket: `tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t`. +- Remove everything on the private socket: `tmux -S "$SOCKET" kill-server`. + +## Helper: create-session.sh + +`./tools/create-session.sh` creates and registers new tmux sessions with automatic registry integration. + +**IMPORTANT**: Before creating a session, ALWAYS run `./tools/list-sessions.sh` to check for existing sessions and ensure your chosen name is unique. + +```bash +./tools/create-session.sh -n [--python|--gdb|--shell] [options] +``` + +**Key options:** +- `-n`/`--name` session name (required) +- `--python` launch Python REPL with PYTHON_BASIC_REPL=1 +- `--gdb` launch gdb debugger +- `--shell` launch bash shell (default) +- `-S`/`--socket` custom socket path (optional, uses default) +- `-w`/`--window` window name (default: "shell") +- `--no-register` don't add to registry + +**Examples:** + +```bash +# Create Python REPL session +./tools/create-session.sh -n claude-python --python + +# Create gdb session +./tools/create-session.sh -n claude-gdb --gdb + +# Create session without registering +./tools/create-session.sh -n temp-session --shell --no-register + +# Create session with custom socket +./tools/create-session.sh -n my-session -S /tmp/custom.sock --python +``` + +**Returns JSON with session info:** +```json +{ + "name": "claude-python", + "socket": "/tmp/claude-tmux-sockets/claude.sock", + "target": "claude-python:0.0", + "type": "python-repl", + "pid": 12345, + "registered": true +} +``` + +## Helper: list-sessions.sh + +`./tools/list-sessions.sh` lists all registered sessions with health status. + +```bash +./tools/list-sessions.sh [--json] +``` + +**Options:** +- `--json` output as JSON instead of table format + +**Table output (default):** +``` +NAME SOCKET TARGET STATUS PID CREATED +claude-python claude.sock :0.0 alive 1234 2h ago +claude-gdb claude.sock :0.0 dead - 1h ago + +Total: 2 | Alive: 1 | Dead: 1 +``` + +**JSON output:** +```json +{ + "sessions": [ + {"name": "claude-python", "status": "alive", ...} + ], + "total": 2, + "alive": 1, + "dead": 1 +} +``` + +**Health statuses:** +- `alive` - Session running and healthy +- `dead` - Pane marked as dead +- `missing` - Session/pane not found +- `zombie` - Process exited but pane exists +- `server` - Tmux server not running + +## Helper: cleanup-sessions.sh + +`./tools/cleanup-sessions.sh` removes dead or old sessions from the registry. + +```bash +./tools/cleanup-sessions.sh [--dry-run] [--all] [--older-than ] +``` + +**Options:** +- `--dry-run` show what would be cleaned without removing +- `--all` remove all sessions (even alive ones) +- `--older-than ` remove sessions older than threshold (e.g., "1h", "2d") + +**Examples:** + +```bash +# Remove dead sessions +./tools/cleanup-sessions.sh + +# Dry-run to see what would be removed +./tools/cleanup-sessions.sh --dry-run + +# Remove sessions inactive for more than 1 hour +./tools/cleanup-sessions.sh --older-than 1h + +# Remove all sessions +./tools/cleanup-sessions.sh --all +``` + +**Duration format:** `30m`, `2h`, `1d`, `3600s` + +## Helper: kill-session.sh + +Kill tmux session and remove from registry (atomic operation). + +**Purpose**: Provides a single operation to fully clean up a session by both killing the tmux session and removing it from the registry. + +**Key features**: +- Atomic operation (kills session AND deregisters) +- Three operation modes: registry lookup, explicit socket/target, auto-detect +- Dry-run support for safety +- Proper exit codes for all scenarios + +**Usage**: +```bash +# Kill session by name (registry lookup) +tools/kill-session.sh -s claude-python + +# Kill with explicit socket and target +tools/kill-session.sh -S /tmp/claude.sock -t my-session:0.0 + +# Auto-detect single session +tools/kill-session.sh + +# Dry-run to see what would happen +tools/kill-session.sh -s claude-python --dry-run +``` + +**Options**: +- `-s NAME` - Session name (uses registry lookup) +- `-S PATH` - Socket path (explicit mode, requires -t) +- `-t TARGET` - Target pane (explicit mode, requires -S) +- `--dry-run` - Show operations without executing +- `-v` - Verbose output +- `-h` - Show help + +**Exit codes**: +- 0 - Complete success (killed AND deregistered) +- 1 - Partial success (one operation succeeded) +- 2 - Complete failure (both failed or not found) +- 3 - Invalid arguments + +**Priority order** (when multiple methods specified): +1. Explicit -S and -t (highest priority) +2. Session name -s (registry lookup) +3. Auto-detect (if no flags and only one session exists) + +**When to use**: +- Cleaning up after interactive debugging sessions +- Removing sessions that are no longer needed +- Ensuring complete cleanup (both tmux and registry) +- Batch operations with proper error handling + +**Notes**: +- Unlike `cleanup-sessions.sh` (which only removes registry entries), this tool also kills the actual tmux session +- Use auto-detect mode when you have only one session and want quick cleanup +- Dry-run mode is helpful to verify what will be cleaned up before executing + +## Helper: safe-send.sh + +`./tools/safe-send.sh` sends keystrokes to tmux panes with automatic retries, readiness checks, and optional prompt waiting. Prevents dropped commands that can occur when sending to busy or not-yet-ready panes. + +```bash +# Session registry mode +./tools/safe-send.sh -s session-name -c "command" [-w pattern] + +# Auto-detect mode (single session) +./tools/safe-send.sh -c "command" [-w pattern] + +# Explicit mode (backward compatible) +./tools/safe-send.sh -t session:0.0 -c "command" [-S socket] [-w pattern] +``` + +**Target selection (priority order):** +- `-s`/`--session` session name (looks up socket/target in registry) +- `-t`/`--target` explicit pane target (session:window.pane) +- (no flags) auto-detect if only one session exists + +**Key options:** +- `-c`/`--command` command to send (required; empty string sends just Enter) +- `-S`/`--socket` tmux socket path (for custom sockets via -S) +- `-L`/`--socket-name` tmux socket name (for named sockets via -L) +- `-l`/`--literal` use literal mode (send text without executing) +- `-m`/`--multiline` use multiline mode (paste-buffer for code blocks) +- `-w`/`--wait` wait for this pattern after sending +- `-T`/`--timeout` timeout in seconds (default: 30) +- `-r`/`--retries` max retry attempts (default: 3) +- `-i`/`--interval` base retry interval in seconds (default: 0.5) +- `-v`/`--verbose` verbose output for debugging + +**Exit codes:** +- `0` - Command sent successfully +- `1` - Failed to send after retries +- `2` - Timeout waiting for prompt +- `3` - Pane not ready +- `4` - Invalid arguments + +**Modes:** +- **Normal mode (default):** Sends command and presses Enter (executes in shell/REPL) +- **Multiline mode (-m):** Sends multiline code blocks via paste-buffer (~10x faster than line-by-line). Auto-appends blank line for Python REPL execution. Incompatible with `-l`. +- **Literal mode (-l):** Sends exact characters without Enter (typing text). Incompatible with `-m`. + +**Use cases:** +- Send commands to Python REPL with automatic retry and prompt waiting +- Send gdb commands and wait for the gdb prompt +- Critical commands that must not be dropped +- Send commands immediately after session creation +- Automate interactions with any interactive CLI tool + +**Examples:** + +```bash +# Send Python command using session registry +./tools/safe-send.sh -s claude-python -c "print('hello')" -w ">>>" -T 10 + +# Auto-detect single session +./tools/safe-send.sh -c "print('world')" -w ">>>" + +# Send text in literal mode (no Enter) +./tools/safe-send.sh -s claude-python -c "some text" -l + +# Send with custom retry settings +./tools/safe-send.sh -s claude-python -c "ls" -r 5 -i 1.0 + +# Send control sequence +./tools/safe-send.sh -s claude-python -c "C-c" + +# Send multiline Python function (fast, preserves indentation) +./tools/safe-send.sh -s claude-python -m -c "def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2)" -w ">>>" -T 10 + +# Send multiline class definition +./tools/safe-send.sh -s claude-python -m -c "class Calculator: + def __init__(self): + self.result = 0 + + def add(self, x): + self.result += x + return self" -w ">>>" + +# Explicit socket/target (backward compatible) +SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets +SOCKET="$SOCKET_DIR/claude.sock" +./tools/safe-send.sh -S "$SOCKET" -t "$SESSION":0.0 -c "print('hello')" -w ">>>" +``` + +**Multiline mode benefits:** +- **~10x faster** than sending line-by-line (single operation vs N separate calls) +- **Preserves indentation** perfectly (important for Python) +- **Auto-executes** in Python REPL (blank line appended automatically) +- **Cleaner logs** (one operation instead of many) +- **Best for:** Function definitions, class definitions, complex code blocks + +## Helper: wait-for-text.sh + +`./tools/wait-for-text.sh` polls a pane for a regex (or fixed string) with a timeout. Works on Linux/macOS with bash + tmux + grep. + +```bash +# Using session name (looks up socket/target from registry) +./tools/wait-for-text.sh -s claude-python -p '^>>>' -T 15 + +# Auto-detect single session (omit -s) +./tools/wait-for-text.sh -p '^>>>' -T 15 + +# Explicit socket/target (backward compatible) +./tools/wait-for-text.sh -S "$SOCKET" -t "$SESSION":0.0 -p '^>>>' -T 15 +``` + +**Target selection (priority order):** +- `-s`/`--session` session name (looks up socket/target in registry) +- `-t`/`--target` explicit pane target (session:window.pane) +- (no flags) auto-detect if only one session exists + +**Options:** +- `-p`/`--pattern` regex to match (required); add `-F` for fixed string +- `-S`/`--socket` tmux socket path (for custom sockets via -S) +- `-T` timeout seconds (integer, default 15) +- `-i` poll interval seconds (default 0.5) +- `-l` history lines to search from the pane (integer, default 1000) +- Exits 0 on first match, 1 on timeout. On failure prints the last captured text to stderr to aid debugging. + +**Examples:** + +```bash +# Wait for Python prompt using session name +./tools/wait-for-text.sh -s claude-python -p '^>>>' -T 10 + +# Wait for gdb prompt with auto-detect +./tools/wait-for-text.sh -p '(gdb)' -T 10 + +# Explicit socket/target (backward compatible) +SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets +SOCKET="$SOCKET_DIR/claude.sock" +./tools/wait-for-text.sh -S "$SOCKET" -t "$SESSION":0.0 -p '^>>>' -T 15 +``` + +## Helper: pane-health.sh + +`./tools/pane-health.sh` checks the health status of a tmux pane before operations to prevent "pane not found" errors and detect failures early. Essential for reliable automation. + +```bash +# Using session name (looks up socket/target from registry) +./tools/pane-health.sh -s claude-python [--format json|text] + +# Auto-detect single session (omit -s) +./tools/pane-health.sh --format text + +# Explicit socket/target (backward compatible) +./tools/pane-health.sh -S "$SOCKET" -t "$SESSION":0.0 [--format json|text] +``` + +**Target selection (priority order):** +- `-s`/`--session` session name (looks up socket/target in registry) +- `-t`/`--target` explicit pane target (session:window.pane) +- (no flags) auto-detect if only one session exists + +**Options:** +- `-S`/`--socket` tmux socket path (for custom sockets via -S) +- `--format` output format: `json` (default) or `text` +- Exits with status codes indicating health state + +**Exit codes:** +- `0` - Healthy (pane alive, process running) +- `1` - Dead (pane marked as dead) +- `2` - Missing (pane/session doesn't exist) +- `3` - Zombie (process exited but pane still exists) +- `4` - Server not running + +**JSON output includes:** +- `status`: overall health (`healthy`, `dead`, `missing`, `zombie`, `server_not_running`) +- `server_running`: boolean +- `session_exists`: boolean +- `pane_exists`: boolean +- `pane_dead`: boolean +- `pid`: process ID (or null) +- `process_running`: boolean + +**Use cases:** +- Before sending commands: verify pane is ready +- After errors: determine if pane crashed +- Periodic health checks during long operations +- Cleanup decision: which panes to kill vs keep + +**Examples:** + +```bash +# Check health using session name (JSON output) +./tools/pane-health.sh -s claude-python +# Output: {"status": "healthy", "server_running": true, ...} + +# Check health with auto-detect (text output) +./tools/pane-health.sh --format text +# Output: Pane claude-python:0.0 is healthy (PID: 12345, process running) + +# Conditional logic with session registry +if ./tools/pane-health.sh -s my-session --format text; then + echo "Pane is ready for commands" + ./tools/safe-send.sh -s my-session -c "print('hello')" +else + echo "Pane is not healthy (exit code: $?)" +fi + +# Explicit socket/target (backward compatible) +SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets +SOCKET="$SOCKET_DIR/claude.sock" +./tools/pane-health.sh -S "$SOCKET" -t "$SESSION":0.0 +``` + +## Advanced: Direct Socket Control + +For advanced users who need explicit control over socket paths without using the session registry, see the [Direct Socket Control](references/direct-socket-control.md) reference. + +This is useful for: +- Custom socket isolation requirements +- Integration with existing tmux workflows +- Testing or debugging tmux configuration + +Most workflows should use the session registry tools described above. + +## Best Practices + +For comprehensive guidance on using the session registry effectively, see: + +- **[Session Registry Reference](references/session-registry.md)** - Complete documentation including: + - Registry architecture and file format + - Advanced usage patterns + - Troubleshooting guide + - Migration from manual socket management + - Best practices for session naming, cleanup strategies, and error handling + - When to use registry vs. manual approach + +Key recommendations: +- Use descriptive session names (e.g., `claude-python-analysis`, not `session1`) +- Run `./tools/cleanup-sessions.sh` periodically to remove dead sessions +- Use `./tools/list-sessions.sh` to verify session health before long operations +- For single-session workflows, omit `-s` flag to leverage auto-detection +- For multiple sessions, always use `-s session-name` for clarity + +## Troubleshooting + +**Session not found in registry:** +- Use `./tools/list-sessions.sh` to see all registered sessions +- Session may have been created with `--no-register` flag +- Registry file may be corrupted (check `$CLAUDE_TMUX_SOCKET_DIR/.sessions.json`) + +**Auto-detection fails with "Multiple sessions found":** +- Specify session name explicitly with `-s my-session` +- Or clean up unused sessions with `./tools/cleanup-sessions.sh` + +**Pane health check fails:** +- Session may have crashed - check with `./tools/list-sessions.sh` +- Tmux server may not be running - verify socket exists +- Use `./tools/pane-health.sh -s session-name --format text` for detailed diagnostics + +**Registry lock timeout:** +- Another process may be writing to registry +- Wait a moment and retry +- Check for stale lock file: `$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock` + +For more detailed troubleshooting, see the [Session Registry Reference](references/session-registry.md#troubleshooting). diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..1fc64ff --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,97 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:dashed/claude-marketplace:plugins/tmux", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "b791a79cde3dc378f5f48d0f4768846c62bc5109", + "treeHash": "c812bb0ddafed26011d6f61daa44be29967ae60526e682cb764e5e47dcc9fe6d", + "generatedAt": "2025-11-28T10:16:03.271236Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "tmux", + "description": "Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output. Use when debugging applications, running interactive REPLs (Python, gdb, ipdb, psql, mysql, node), automating terminal workflows, or when user mentions tmux, debugging, or interactive shells.", + "version": "1.4.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "f2fdf0bf02e8605e10c4599cb22e8ad567c432cb2edab0ed69bd41d03ef7b8a8" + }, + { + "path": "SKILL.md", + "sha256": "8d54919da5b2f19c4c5f56f70f78d1a911f87b648b91cab68f9a2e1b9871c70b" + }, + { + "path": "tools/list-sessions.sh", + "sha256": "5cf212b88d78ba58f3d43b9c36d2479ba37819d4ce7fdd4f2ac3af75f8565d40" + }, + { + "path": "tools/kill-session.sh", + "sha256": "42a2b18ca43402e0e9880edb97c56cb49a0ee90501a904b357a6267dc8a15e02" + }, + { + "path": "tools/wait-for-text.sh", + "sha256": "7fa8851b3be0d49abb49c4fbdaf31e687036ebf1d40030e66e1d5a7d3d72298a" + }, + { + "path": "tools/pane-health.sh", + "sha256": "c43f562af38359e26558c2a5548753c0b245549f828082ad4473db734f795a95" + }, + { + "path": "tools/safe-send.sh", + "sha256": "f505adb563854400da13f8b59f5de782292301e7736849d9d2a2766b1eb9ab2d" + }, + { + "path": "tools/create-session.sh", + "sha256": "066417e810ff7b84200bba96edba07257c920f9d2bfa4eaa34e95bddb2c23cb5" + }, + { + "path": "tools/find-sessions.sh", + "sha256": "ecfad81dbb7c9aaecb03fbd01f5175902916be18d68184589145c2ebd5585b35" + }, + { + "path": "tools/cleanup-sessions.sh", + "sha256": "2b9dd4b78738b341579c34b032c3898284571cc418bac04d25493c69fb64264c" + }, + { + "path": "tools/lib/registry.sh", + "sha256": "3ce5fcf343910795e492a6e7b952fd12ba59624be0aaa36271da69ad77201739" + }, + { + "path": "tools/lib/time_utils.sh", + "sha256": "526ffe7bcc4506b0b810d1c6a023bcce8fda22d3e9086eddb926e354729ba869" + }, + { + "path": "references/session-lifecycle.md", + "sha256": "489dd66ffbada2c49bb4b3b0ddc20daf2edddba1bf6af01957a4ea5719aa658c" + }, + { + "path": "references/session-registry.md", + "sha256": "b2834deabbaabc2ed2b2e8a6e1ee3abb2371776461b9ca01a120a36522636785" + }, + { + "path": "references/direct-socket-control.md", + "sha256": "936d7dabdc75bd8e8b10ff4513740d3a9e54757892c283c828557c86fc3b2b4b" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "59f1b332b5913768c9aa80aa3f12f622fef6daf082e2b8ef9053191ec3ea3b50" + } + ], + "dirSha256": "c812bb0ddafed26011d6f61daa44be29967ae60526e682cb764e5e47dcc9fe6d" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/references/direct-socket-control.md b/references/direct-socket-control.md new file mode 100644 index 0000000..2008b13 --- /dev/null +++ b/references/direct-socket-control.md @@ -0,0 +1,108 @@ +# Direct Socket Control + +This guide covers using direct tmux commands for session management without the session registry. This is an advanced approach for users who need explicit control over socket paths and session management. + +**Note:** The session registry is recommended for most workflows. It eliminates ~80% of boilerplate and provides automatic session tracking, health checking, and cleanup. See [Session Registry Reference](session-registry.md) for the standard approach. + +## When to Use This Approach + +Use direct socket control when you need: +- Custom socket isolation requirements +- Integration with existing tmux workflows +- Multiple sessions on different sockets with complex routing +- Testing or debugging tmux configuration + +For most interactive development, debugging, and REPL workflows, use the session registry tools instead. + +## Manual Setup + +```bash +SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets # well-known dir for all agent sockets +mkdir -p "$SOCKET_DIR" +SOCKET="$SOCKET_DIR/claude.sock" # keep agent sessions separate from your personal tmux +SESSION=claude-python # slug-like names; avoid spaces +tmux -S "$SOCKET" new -d -s "$SESSION" -n shell +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter +tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 # watch output +tmux -S "$SOCKET" kill-session -t "$SESSION" # clean up +``` + +After starting a session, ALWAYS tell the user how to monitor it: + +``` +To monitor this session yourself: + tmux -S "$SOCKET" attach -t claude-python + +Or to capture the output once: + tmux -S "$SOCKET" capture-pane -p -J -t claude-python:0.0 -S -200 +``` + +## Socket Convention + +- Agents MUST place tmux sockets under `CLAUDE_TMUX_SOCKET_DIR` (defaults to `${TMPDIR:-/tmp}/claude-tmux-sockets`) and use `tmux -S "$SOCKET"` so we can enumerate/clean them. Create the dir first: `mkdir -p "$CLAUDE_TMUX_SOCKET_DIR"`. +- Default socket path to use unless you must isolate further: `SOCKET="$CLAUDE_TMUX_SOCKET_DIR/claude.sock"`. + +## Targeting Panes and Naming + +- Target format: `{session}:{window}.{pane}`, defaults to `:0.0` if omitted. Keep names short (e.g., `claude-py`, `claude-gdb`). +- Use `-S "$SOCKET"` consistently to stay on the private socket path. If you need user config, drop `-f /dev/null`; otherwise `-f /dev/null` gives a clean config. +- Inspect: `tmux -S "$SOCKET" list-sessions`, `tmux -S "$SOCKET" list-panes -a`. + +## Finding Sessions Manually + +- List sessions on a specific socket: `./tools/find-sessions.sh -S "$SOCKET"`; add `-q partial-name` to filter. +- Scan all sockets: `./tools/find-sessions.sh --all` (uses `CLAUDE_TMUX_SOCKET_DIR`) + +## Direct tmux send-keys + +- Prefer literal sends to avoid shell splitting: `tmux -S "$SOCKET" send-keys -t target -l -- "$cmd"` +- When composing inline commands, use single quotes or ANSI C quoting to avoid expansion: `tmux ... send-keys -t target -- $'python3 -m http.server 8000'`. +- To send control keys: `tmux ... send-keys -t target C-c`, `C-d`, `C-z`, `Escape`, etc. + +## Comparison with Session Registry + +| Feature | Direct Socket Control | Session Registry | +|---------|----------------------|------------------| +| Setup complexity | Manual, verbose | Automated with tools | +| Socket/target specification | Required every time | Once at creation | +| Session discovery | Manual enumeration | Automatic tracking | +| Health checking | Manual verification | Built-in health status | +| Cleanup | Manual kill commands | Automated cleanup tools | +| Auto-detection | Not available | Single session auto-detect | +| Best for | Custom isolation, CI/CD | Interactive workflows | + +## Migrating to Session Registry + +If you're using direct socket control and want to migrate to the registry approach: + +1. **Create sessions with registry tools:** + ```bash + # Instead of: + # tmux -S "$SOCKET" new -d -s my-session -n shell + # Use: + ./tools/create-session.sh -n my-session --shell + ``` + +2. **Replace socket/target with session name:** + ```bash + # Instead of: + # ./tools/safe-send.sh -S "$SOCKET" -t "$SESSION":0.0 -c "command" + # Use: + ./tools/safe-send.sh -s my-session -c "command" + ``` + +3. **Use registry for cleanup:** + ```bash + # Instead of: + # tmux -S "$SOCKET" kill-session -t "$SESSION" + # Use: + ./tools/cleanup-sessions.sh + ``` + +See the [Migration Guide](session-registry.md#migration-guide) in the Session Registry Reference for complete migration instructions. + +## See Also + +- [Session Registry Reference](session-registry.md) - The recommended approach for most workflows +- [Session Registry: Best Practices](session-registry.md#best-practices) - When to use registry vs. manual +- [Migration Guide](session-registry.md#migration-guide) - Transition from manual to registry approach diff --git a/references/session-lifecycle.md b/references/session-lifecycle.md new file mode 100644 index 0000000..8d9f2ce --- /dev/null +++ b/references/session-lifecycle.md @@ -0,0 +1,503 @@ +# Session Lifecycle Guide + +> Practical guide for managing tmux sessions through their complete lifecycle - from creation to cleanup + +## Overview + +Tmux sessions managed by this skill follow a predictable lifecycle. Understanding these stages helps you make better decisions about when to create, reuse, or clean up sessions. This guide provides real-world workflows and decision trees for daily use. + +**When to use this guide:** +- You're unsure whether to create a new session or reuse an existing one +- You need to debug a crashed or stuck session +- You want to understand proper cleanup workflows +- You're managing multiple parallel sessions + +## Lifecycle Stages + +``` +┌─────────────┐ +│ PRE-CHECK │ Check existing sessions +│ (optional) │ Tool: list-sessions.sh +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ CREATE │ Spawn new tmux session +│ │ Tool: create-session.sh +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ INIT │ Wait for startup (prompt appears) +│ │ Tool: wait-for-text.sh +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ ACTIVE USE │ Send commands, capture output +│ │ Tools: safe-send.sh, wait-for-text.sh +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ IDLE │ Session exists but unused +│ │ Tool: list-sessions.sh (check status) +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ CLEANUP │ Remove dead/stale sessions +│ │ Tools: cleanup-sessions.sh, tmux kill-session +└─────────────┘ +``` + +**Error recovery:** +- If session crashes → Check with `pane-health.sh` +- If session becomes zombie → Remove with `cleanup-sessions.sh` +- If network interruption → Reconnect using `tmux attach` or resume with session name + +## Common Workflows + +### 1. Quick Python Calculation (Ephemeral Session) + +**Use case:** Run a one-off calculation, get the result, and cleanup immediately. + +**Duration:** < 5 minutes + +**Commands:** +```bash +# 1. Check no conflicts +./tools/list-sessions.sh + +# 2. Create session +./tools/create-session.sh -n quick-calc --python + +# 3. Send calculation +./tools/safe-send.sh -s quick-calc -c "import math; print(math.factorial(100))" -w ">>>" -T 10 + +# 4. Capture output +SOCKET=$(jq -r '.sessions["quick-calc"].socket' ~/.local/state/claude-tmux/.sessions.json) +tmux -S "$SOCKET" capture-pane -p -t quick-calc:0.0 -S -10 + +# 5. Cleanup immediately +tmux -S "$SOCKET" kill-session -t quick-calc +./tools/cleanup-sessions.sh +``` + +**When to use:** One-time calculations, quick tests, disposable experiments. + +### 2. Long-Running Analysis (Persistent Session) + +**Use case:** Hours or days of interactive REPL work with state preservation. + +**Duration:** Hours to days + +**Commands:** +```bash +# 1. Check existing sessions first +./tools/list-sessions.sh + +# 2. Create persistent session with descriptive name +./tools/create-session.sh -n data-analysis-2025-11 --python + +# 3. Load data (may take time) +./tools/safe-send.sh -s data-analysis-2025-11 -c "import pandas as pd" -w ">>>" +./tools/safe-send.sh -s data-analysis-2025-11 -c "df = pd.read_csv('large_dataset.csv')" -w ">>>" -T 300 + +# 4. Work interactively over time +./tools/safe-send.sh -s data-analysis-2025-11 -c "df.describe()" -w ">>>" +# ... hours later ... +./tools/safe-send.sh -s data-analysis-2025-11 -c "df.groupby('category').mean()" -w ">>>" + +# 5. Check session health periodically +./tools/pane-health.sh -s data-analysis-2025-11 + +# 6. Cleanup only when done (days later) +./tools/list-sessions.sh # Verify it's the right session +tmux -S "$(jq -r '.sessions["data-analysis-2025-11"].socket' ~/.local/state/claude-tmux/.sessions.json)" kill-session -t data-analysis-2025-11 +./tools/cleanup-sessions.sh +``` + +**When to use:** Long-running analysis, state preservation, multiple work sessions. + +**Best practices:** +- Use descriptive names (include date or project name) +- Check health before resuming work +- Don't cleanup until completely done +- Capture important output early + +### 3. Recovering from Crashed Session + +**Use case:** Session died unexpectedly, need to investigate and restart. + +**Duration:** 5-10 minutes + +**Commands:** +```bash +# 1. Check session status +./tools/list-sessions.sh +# Output shows: status "dead" or "zombie" + +# 2. Diagnose the issue +./tools/pane-health.sh -s crashed-session --format text +# Check exit code: 1=dead, 2=missing, 3=zombie + +# 3. Capture any remaining output for debugging +SOCKET=$(jq -r '.sessions["crashed-session"].socket' ~/.local/state/claude-tmux/.sessions.json) +tmux -S "$SOCKET" capture-pane -p -t crashed-session:0.0 -S -200 > crash-output.txt 2>/dev/null || echo "Pane unavailable" + +# 4. Remove dead session from registry +./tools/cleanup-sessions.sh # Removes dead sessions automatically + +# 5. Create replacement session +./tools/create-session.sh -n crashed-session-recovery --python + +# 6. Resume work with new session +./tools/safe-send.sh -s crashed-session-recovery -c "# Resuming from crash..." -w ">>>" +``` + +**When to use:** Session crashed, process died, debugging needed. + +**Troubleshooting tips:** +- Always capture output before cleanup (may contain error messages) +- Check system logs if crash is mysterious: `dmesg`, `journalctl` +- Verify PYTHON_BASIC_REPL=1 was set (common cause of silent failures) + +### 4. Multi-Session Workspace (Parallel Tasks) + +**Use case:** Multiple parallel tasks (data loading, analysis, monitoring) in separate sessions. + +**Duration:** Variable + +**Commands:** +```bash +# 1. Check existing sessions +./tools/list-sessions.sh + +# 2. Create multiple sessions with clear names +./tools/create-session.sh -n loader-session --python +./tools/create-session.sh -n analysis-session --python +./tools/create-session.sh -n monitor-session --python + +# 3. Start parallel work +# Session 1: Load large dataset +./tools/safe-send.sh -s loader-session -c "import pandas as pd; df = pd.read_csv('huge.csv')" -w ">>>" -T 600 & + +# Session 2: Run analysis on existing data +./tools/safe-send.sh -s analysis-session -c "results = analyze_data()" -w ">>>" -T 300 & + +# Session 3: Monitor system +./tools/safe-send.sh -s monitor-session -c "import psutil; psutil.cpu_percent()" -w ">>>" + +# 4. Check all sessions status +./tools/list-sessions.sh + +# 5. Cleanup when done +./tools/cleanup-sessions.sh --all # Or cleanup individually +``` + +**When to use:** Parallel independent tasks, workspace organization, resource isolation. + +**Best practices:** +- Use descriptive session names (purpose-based: loader-*, analysis-*, monitor-*) +- One session per major task (don't overload single session) +- Check `list-sessions.sh` frequently to monitor all sessions +- Cleanup all related sessions together when project complete + +## Decision Trees + +### Should I Create a New Session or Reuse? + +``` +Start + │ + ├─→ Is this a quick one-off task? + │ └─→ YES → Create new ephemeral session + │ (cleanup immediately after) + │ + ├─→ Do I have an existing session for this work? + │ ├─→ YES → Is it still alive? + │ │ ├─→ YES → Reuse existing session + │ │ └─→ NO → Create new session + │ └─→ NO → Create new session + │ + └─→ Am I starting a new long-running project? + └─→ YES → Create new persistent session + (descriptive name with date/project) +``` + +**Rules of thumb:** +- ✅ **Create new** for: Different projects, parallel tasks, isolation needed +- ✅ **Reuse** for: Continuing same work, state preservation needed +- ❌ **Don't reuse** for: Unrelated tasks (state pollution), different projects + +### When Should I Clean Up Sessions? + +``` +Start + │ + ├─→ Is session marked as "dead" or "zombie"? + │ └─→ YES → Cleanup immediately + │ (./tools/cleanup-sessions.sh) + │ + ├─→ Has the task completed? + │ └─→ YES → Cleanup immediately + │ (tmux kill-session + cleanup-sessions.sh) + │ + ├─→ Is session idle for > 1 hour? + │ ├─→ AND no state preservation needed? + │ │ └─→ YES → Cleanup + │ └─→ OR state needed? + │ └─→ NO → Keep (long-running analysis) + │ + └─→ Am I done for the day but continuing tomorrow? + └─→ Keep session (resume later) +``` + +**Cleanup commands:** +```bash +# Remove only dead/zombie sessions (safe, default) +./tools/cleanup-sessions.sh + +# Remove sessions older than 1 hour +./tools/cleanup-sessions.sh --older-than 1h + +# Remove all sessions (careful!) +./tools/cleanup-sessions.sh --all + +# Preview what would be removed (dry-run) +./tools/cleanup-sessions.sh --dry-run +``` + +### How Should I Handle Session Errors? + +``` +Error Detected + │ + ├─→ Is session responding to commands? + │ ├─→ NO → Check health (pane-health.sh) + │ │ ├─→ Dead/zombie → Capture output → Cleanup → Recreate + │ │ └─→ Alive but hung → Send C-c → Retry command + │ └─→ YES → Problem is elsewhere (not session) + │ + ├─→ Are commands being executed but output wrong? + │ └─→ Check PYTHON_BASIC_REPL=1 was set + │ └─→ Not set? Restart session with env var + │ + └─→ Is prompt not appearing? + └─→ Increase timeout (-T flag) + └─→ Still failing? Check pane content manually +``` + +## Tool Reference Matrix + +| Lifecycle Stage | Tool | Purpose | Example Command | +|-----------------|------|---------|-----------------| +| **Pre-check** | `list-sessions.sh` | Check existing sessions | `./tools/list-sessions.sh` | +| **Create** | `create-session.sh` | Spawn new session | `./tools/create-session.sh -n my-session --python` | +| **Init** | `wait-for-text.sh` | Wait for startup prompt | `./tools/wait-for-text.sh -s my-session -p ">>>" -T 15` | +| **Active Use** | `safe-send.sh` | Send commands safely | `./tools/safe-send.sh -s my-session -c "print(42)" -w ">>>"` | +| **Active Use** | `safe-send.sh` (multiline) | Send code blocks | `./tools/safe-send.sh -s my-session -m -c "def foo():\n return 42" -w ">>>"` | +| **Monitoring** | `pane-health.sh` | Check session health | `./tools/pane-health.sh -s my-session` | +| **Monitoring** | `list-sessions.sh` | View all sessions | `./tools/list-sessions.sh --json` | +| **Capture** | `tmux capture-pane` | Get session output | `tmux -S $SOCKET capture-pane -p -t session:0.0 -S -100` | +| **Cleanup** | `cleanup-sessions.sh` | Remove dead sessions | `./tools/cleanup-sessions.sh` | +| **Cleanup** | `tmux kill-session` | Terminate specific session | `tmux -S $SOCKET kill-session -t my-session` | + +## Troubleshooting Quick Reference + +### Session Not Found + +**Symptom:** `./tools/safe-send.sh -s my-session -c "cmd"` fails with "Session 'my-session' not found in registry" + +**Causes:** +1. Session was never created +2. Session was created but not registered (`--no-register` flag used) +3. Session was cleaned up by mistake + +**Fixes:** +```bash +# Check what sessions exist +./tools/list-sessions.sh + +# Check if session exists outside registry +./tools/find-sessions.sh --all + +# Recreate session +./tools/create-session.sh -n my-session --python +``` + +### Commands Not Executing + +**Symptom:** Commands sent but REPL shows no activity + +**Causes:** +1. PYTHON_BASIC_REPL=1 not set (most common!) +2. Session crashed/dead +3. Prompt not detected correctly + +**Fixes:** +```bash +# Check session health +./tools/pane-health.sh -s my-session --format text + +# Manually check what's in the pane +SOCKET=$(jq -r '.sessions["my-session"].socket' ~/.local/state/claude-tmux/.sessions.json) +tmux -S "$SOCKET" capture-pane -p -t my-session:0.0 -S -20 + +# If Python REPL, verify PYTHON_BASIC_REPL=1 was set +# Symptom: You'll see fancy colored prompts instead of plain >>> +# Fix: Kill and recreate with correct env var +``` + +### Output Capture Issues + +**Symptom:** `tmux capture-pane` returns incomplete or truncated output + +**Causes:** +1. Pane history buffer too small +2. Lines wrapped due to terminal width +3. ANSI color codes interfering + +**Fixes:** +```bash +# Capture more history (-S flag = start line) +tmux -S "$SOCKET" capture-pane -p -t session:0.0 -S -500 + +# Join wrapped lines (-J flag) +tmux -S "$SOCKET" capture-pane -p -J -t session:0.0 -S -200 + +# Strip ANSI color codes +tmux -S "$SOCKET" capture-pane -p -t session:0.0 | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' +``` + +### Dead/Zombie Sessions + +**Symptom:** `./tools/list-sessions.sh` shows status "dead" or "zombie" + +**Causes:** +1. Process crashed +2. Session was killed manually +3. Out of memory / system issue + +**Fixes:** +```bash +# Capture any remaining output for debugging +SOCKET=$(jq -r '.sessions["dead-session"].socket' ~/.local/state/claude-tmux/.sessions.json) +tmux -S "$SOCKET" capture-pane -p -t dead-session:0.0 -S -500 > debug.txt 2>/dev/null + +# Remove from registry +./tools/cleanup-sessions.sh + +# Recreate if needed +./tools/create-session.sh -n replacement-session --python +``` + +### Multiple Sessions with Same Name + +**Symptom:** `./tools/create-session.sh -n my-session --python` fails with "session already exists" + +**Causes:** +1. Forgot to check existing sessions first +2. Previous session not cleaned up + +**Fixes:** +```bash +# Check existing sessions +./tools/list-sessions.sh + +# Use different name OR cleanup old session first +tmux -S "$SOCKET" kill-session -t my-session +./tools/cleanup-sessions.sh + +# Then create new session +./tools/create-session.sh -n my-session --python +``` + +## Best Practices + +### ✅ DO + +1. **Always check before creating** + ```bash + ./tools/list-sessions.sh # Check for conflicts first + ``` + +2. **Use descriptive session names** + ```bash + # Good: data-analysis-2025-11, debug-api-auth, monitor-prod + # Bad: session1, test, tmp + ``` + +3. **Set PYTHON_BASIC_REPL=1 for Python** + ```bash + # This is handled by create-session.sh --python automatically + # But verify if creating manually + ``` + +4. **Wait for prompts after every command** + ```bash + ./tools/safe-send.sh -s session -c "command" -w ">>>" -T 10 + # Always include -w flag for synchronization + ``` + +5. **Use session registry (-s flag)** + ```bash + # Preferred: ./tools/safe-send.sh -s my-session -c "cmd" + # Avoid: ./tools/safe-send.sh -S $SOCKET -t session:0.0 -c "cmd" + ``` + +6. **Check health before critical operations** + ```bash + if ./tools/pane-health.sh -s session; then + ./tools/safe-send.sh -s session -c "critical_command" + fi + ``` + +7. **Use multiline mode for code blocks** + ```bash + # 10x faster than line-by-line + ./tools/safe-send.sh -s session -m -c "def foo():\n return 42" -w ">>>" + ``` + +8. **Cleanup dead sessions regularly** + ```bash + # Run periodically or in cleanup scripts + ./tools/cleanup-sessions.sh + ``` + +9. **Capture output early and often** + ```bash + # Before making destructive changes + tmux -S "$SOCKET" capture-pane -p -t session:0.0 -S -200 > backup.txt + ``` + +10. **Use --dry-run for cleanup preview** + ```bash + # See what would be removed before executing + ./tools/cleanup-sessions.sh --dry-run + ./tools/cleanup-sessions.sh --older-than 2d --dry-run + ``` + +### ❌ DON'T + +1. **Don't create sessions without checking first** - Leads to name conflicts +2. **Don't reuse sessions for unrelated work** - State pollution causes bugs +3. **Don't forget -w flag when sending commands** - Race conditions +4. **Don't skip health checks before critical operations** - Pane might be dead +5. **Don't use generic session names** - Hard to manage multiple sessions +6. **Don't leave dead sessions in registry** - Clutters output, wastes resources +7. **Don't forget to cleanup ephemeral sessions** - Resource leaks +8. **Don't send commands too fast** - Wait for prompts between commands +9. **Don't ignore session health warnings** - Investigate issues early +10. **Don't use line-by-line for large code blocks** - Use multiline mode instead + +## Related References + +- [Session Registry Guide](./session-registry.md) - Deep dive on registry system +- [Direct Socket Control](./direct-socket-control.md) - Advanced manual socket management +- [Main SKILL.md](../SKILL.md) - Complete tmux skill documentation + +--- + +**Version:** Documented for tmux skill v1.3.0+ (includes multiline support) diff --git a/references/session-registry.md b/references/session-registry.md new file mode 100644 index 0000000..992572c --- /dev/null +++ b/references/session-registry.md @@ -0,0 +1,1484 @@ +# Session Registry Reference + +This document provides comprehensive documentation for the tmux skill session registry system. + +## Table of Contents + +1. [Overview](#overview) +2. [Architecture](#architecture) +3. [Registry File Format](#registry-file-format) +4. [Tool Reference](#tool-reference) +5. [Session Resolution](#session-resolution) +6. [Troubleshooting](#troubleshooting) +7. [Migration Guide](#migration-guide) +8. [Best Practices](#best-practices) +9. [Advanced Patterns](#advanced-patterns) + +--- + +## Overview + +The session registry is an automatic session tracking system that eliminates ~80% of boilerplate when working with tmux sessions. Instead of repeatedly specifying socket paths and targets, sessions are registered once and can be referenced by name. + +### Key Benefits + +- **Reduced boilerplate**: No more repeating `-S socket -t target` on every command +- **Automatic discovery**: Tools auto-detect single sessions when no name specified +- **Health tracking**: Built-in integration with pane health checks +- **Activity tracking**: Automatic timestamps for cleanup decisions +- **Backward compatible**: All existing workflows continue to work + +### Quick Example + +**Before (manual approach):** +```bash +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +SESSION="my-python" +tmux -S "$SOCKET" new -d -s "$SESSION" -n shell +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 'PYTHON_BASIC_REPL=1 python3 -q' Enter +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 'print("hello")' Enter +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 'print("world")' Enter +``` + +**After (registry approach):** +```bash +./tools/create-session.sh -n my-python --python +./tools/safe-send.sh -s my-python -c 'print("hello")' -w ">>>" +./tools/safe-send.sh -c 'print("world")' -w ">>>" # Auto-detects single session +``` + +--- + +## Architecture + +### Components + +The session registry consists of: + +1. **Registry Library** (`tools/lib/registry.sh`) + - Core CRUD operations + - Portable file locking + - JSON validation + - Activity tracking + +2. **Registry File** (`$CLAUDE_TMUX_SOCKET_DIR/.sessions.json`) + - JSON database of registered sessions + - Atomic updates with write-then-move + - Human-readable format + +3. **Management Tools** + - `create-session.sh` - Create and register sessions + - `list-sessions.sh` - View all sessions with health status + - `cleanup-sessions.sh` - Remove dead/old sessions + +4. **Enhanced Tools** + - `safe-send.sh` - Send commands by session name + - `wait-for-text.sh` - Wait for patterns by session name *(planned)* + - `pane-health.sh` - Check health by session name *(planned)* + +### File Locking Mechanism + +The registry uses portable locking to ensure atomic operations: + +**On Linux (flock available):** +- Uses kernel-level `flock` for fast, reliable locking +- Lock file: `$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock` +- Timeout: 5 seconds (configurable via `LOCK_TIMEOUT`) + +**On macOS (flock not available):** +- Falls back to mkdir-based locking (atomic directory creation) +- Lock directory: `$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock/` +- Contains owner info for debugging +- Same timeout behavior as flock + +**Lock acquisition:** +```bash +# Try to acquire lock with timeout +registry_lock || return 75 # Exit code 75 = lock timeout + +# ... perform registry operations ... + +# Always release lock +registry_unlock +``` + +### Atomic Updates + +All registry modifications use an atomic write-then-move pattern: + +1. **Lock** - Acquire registry lock +2. **Read** - Load current registry data +3. **Modify** - Make changes to in-memory copy +4. **Write** - Write to temporary file +5. **Validate** - Verify JSON with `jq` +6. **Move** - Atomically move temp file to registry (if valid) +7. **Unlock** - Release registry lock + +This ensures the registry is never corrupted, even if a process crashes mid-update. + +--- + +## Registry File Format + +### Location + +The registry file is located at: +```bash +$CLAUDE_TMUX_SOCKET_DIR/.sessions.json +``` + +Where `CLAUDE_TMUX_SOCKET_DIR` defaults to: +```bash +${TMPDIR:-/tmp}/claude-tmux-sockets +``` + +### JSON Schema + +The registry is a JSON file with the following structure: + +```json +{ + "sessions": { + "session-name": { + "socket": "/path/to/socket", + "target": "session:window.pane", + "type": "python-repl|debugger|shell", + "pid": 12345, + "created_at": "2025-11-23T10:30:00Z", + "last_active": "2025-11-23T12:45:00Z" + } + } +} +``` + +### Field Descriptions + +| Field | Type | Description | +|-------|------|-------------| +| `socket` | string | Absolute path to tmux socket file | +| `target` | string | Tmux target in format `session:window.pane` | +| `type` | string | Session type: `python-repl`, `debugger`, `shell` | +| `pid` | number | Process ID of the pane (if available) | +| `created_at` | string | ISO8601 timestamp of session creation | +| `last_active` | string | ISO8601 timestamp of last use | + +### Example Registry File + +```json +{ + "sessions": { + "claude-python": { + "socket": "/tmp/claude-tmux-sockets/claude.sock", + "target": "claude-python:0.0", + "type": "python-repl", + "pid": 45678, + "created_at": "2025-11-23T10:00:00Z", + "last_active": "2025-11-23T15:30:00Z" + }, + "debug-api": { + "socket": "/tmp/claude-tmux-sockets/claude.sock", + "target": "debug-api:0.0", + "type": "debugger", + "pid": 45789, + "created_at": "2025-11-23T11:00:00Z", + "last_active": "2025-11-23T14:20:00Z" + } + } +} +``` + +--- + +## Tool Reference + +### create-session.sh + +Create and register new tmux sessions with automatic registry integration. + +#### Usage + +```bash +./tools/create-session.sh -n [options] +``` + +#### Options + +| Flag | Description | +|------|-------------| +| `-n, --name ` | Session name (required) | +| `-S, --socket ` | Custom socket path (optional, uses default) | +| `-w, --window ` | Window name (default: "shell") | +| `--python` | Launch Python REPL with `PYTHON_BASIC_REPL=1` | +| `--gdb` | Launch gdb debugger | +| `--shell` | Launch bash shell (default) | +| `--no-register` | Don't add to registry | +| `-h, --help` | Show help message | + +#### Session Types + +**`--shell` (default)** +- Launches bash shell +- General-purpose interactive environment +- Type recorded as: `shell` + +**`--python`** +- Launches Python REPL with `PYTHON_BASIC_REPL=1` set +- Critical for compatibility with tmux send-keys +- Disables fancy prompt/highlighting that interferes with automation +- Type recorded as: `python-repl` + +**`--gdb`** +- Launches gdb debugger +- Automatically sets `set pagination off` recommended +- Type recorded as: `debugger` + +#### Exit Codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | Invalid arguments | +| 2 | Session already exists | +| 3 | Tmux command failed | +| 4 | Registry operation failed | + +#### JSON Output + +Returns session information as JSON: + +```json +{ + "name": "my-python", + "socket": "/tmp/claude-tmux-sockets/claude.sock", + "target": "my-python:0.0", + "type": "python-repl", + "pid": 12345, + "window": "shell", + "registered": true +} +``` + +#### Examples + +**Create Python REPL session:** +```bash +./tools/create-session.sh -n claude-python --python +``` + +**Create gdb session:** +```bash +./tools/create-session.sh -n debug-app --gdb +``` + +**Create session with custom socket:** +```bash +./tools/create-session.sh -n isolated-session -S /tmp/custom.sock --shell +``` + +**Create without registering (one-off session):** +```bash +./tools/create-session.sh -n temp-work --shell --no-register +``` + +**Create with custom window name:** +```bash +./tools/create-session.sh -n data-analysis -w "jupyter" --python +``` + +#### When to Use + +- **With registration (default)**: Interactive development, debugging, exploration +- **Without registration (`--no-register`)**: Temporary sessions, scripts that manage cleanup, CI/CD + +--- + +### list-sessions.sh + +List all registered sessions with health status information. + +#### Usage + +```bash +./tools/list-sessions.sh [--json] +``` + +#### Options + +| Flag | Description | +|------|-------------| +| `--json` | Output as JSON instead of table format | +| `-h, --help` | Show help message | + +#### Output Formats + +**Table format (default):** +``` +NAME SOCKET TARGET STATUS PID CREATED +claude-python claude.sock :0.0 alive 1234 2h ago +debug-api claude.sock :0.0 dead - 1h ago + +Total: 2 | Alive: 1 | Dead: 1 +``` + +**JSON format (`--json`):** +```json +{ + "sessions": [ + { + "name": "claude-python", + "socket": "/tmp/claude-tmux-sockets/claude.sock", + "socket_basename": "claude.sock", + "target": "claude-python:0.0", + "type": "python-repl", + "status": "alive", + "pid": 1234, + "created_at": "2025-11-23T10:00:00Z" + } + ], + "total": 2, + "alive": 1, + "dead": 1 +} +``` + +#### Health Statuses + +| Status | Meaning | Exit Code from pane-health.sh | +|--------|---------|-------------------------------| +| `alive` | Session is running and healthy | 0 | +| `dead` | Pane is marked as dead | 1 | +| `missing` | Session/pane not found in tmux | 2 | +| `zombie` | Process exited but pane exists | 3 | +| `server` | Tmux server not running | 4 | +| `unknown` | pane-health.sh not available | - | + +#### Examples + +**List sessions in table format:** +```bash +./tools/list-sessions.sh +``` + +**List sessions as JSON for scripting:** +```bash +./tools/list-sessions.sh --json | jq '.sessions[] | select(.status == "alive")' +``` + +**Count alive sessions:** +```bash +./tools/list-sessions.sh --json | jq '.alive' +``` + +**Get all Python sessions:** +```bash +./tools/list-sessions.sh --json | jq '.sessions[] | select(.type == "python-repl")' +``` + +#### When to Use + +- Check what sessions are currently registered +- Verify session health before operations +- Monitor session activity and age +- Identify dead sessions before cleanup +- Generate reports or dashboards + +--- + +### cleanup-sessions.sh + +Remove dead or old sessions from the registry. + +#### Usage + +```bash +./tools/cleanup-sessions.sh [options] +``` + +#### Options + +| Flag | Description | +|------|-------------| +| `--dry-run` | Show what would be cleaned without removing | +| `--all` | Remove all sessions (even alive ones) | +| `--older-than ` | Remove sessions older than threshold | +| `-h, --help` | Show help message | + +#### Duration Format + +Supported units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days) + +Examples: +- `30m` - 30 minutes +- `2h` - 2 hours +- `1d` - 1 day +- `3600s` - 3600 seconds (1 hour) + +#### Cleanup Modes + +**Default mode (no flags):** +- Removes only dead/missing/zombie sessions +- Safest option +- Preserves all healthy sessions + +**Age-based mode (`--older-than`):** +- Removes sessions older than specified duration +- Based on `created_at` timestamp +- Can combine with default mode (dead OR old) + +**All mode (`--all`):** +- Removes all sessions regardless of health +- Use with caution +- Good for "clean slate" scenarios + +#### Exit Codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | Invalid arguments | + +#### Examples + +**Remove dead sessions (safe default):** +```bash +./tools/cleanup-sessions.sh +``` + +**Dry-run to preview cleanup:** +```bash +./tools/cleanup-sessions.sh --dry-run +``` + +**Remove sessions inactive for more than 1 hour:** +```bash +./tools/cleanup-sessions.sh --older-than 1h +``` + +**Remove sessions older than 2 days:** +```bash +./tools/cleanup-sessions.sh --older-than 2d +``` + +**Remove all sessions (clean slate):** +```bash +./tools/cleanup-sessions.sh --all +``` + +**Combine dry-run with age filter:** +```bash +./tools/cleanup-sessions.sh --dry-run --older-than 1d +``` + +#### When to Use + +- **Manual cleanup**: After finishing work with sessions +- **Periodic cleanup**: Cron job to remove old sessions +- **Aggressive cleanup**: Before starting new work session +- **Debugging**: Dry-run to see what's stale + +#### Automation Examples + +**Daily cleanup via cron:** +```bash +# Remove sessions older than 1 day, runs daily at 3am +0 3 * * * cd /path/to/tmux/tools && ./cleanup-sessions.sh --older-than 1d +``` + +**Pre-work cleanup script:** +```bash +#!/bin/bash +# Clean slate before starting work +./tools/cleanup-sessions.sh --all +./tools/create-session.sh -n work-python --python +./tools/create-session.sh -n work-gdb --gdb +``` + +--- + +### safe-send.sh (Session Registry Features) + +The `safe-send.sh` tool has been enhanced with session registry support. + +#### Session Resolution Options + +**Three ways to specify the target:** + +1. **Session name** (`-s`): Look up socket/target from registry +2. **Explicit** (`-S` + `-t`): Specify socket and target directly (backward compatible) +3. **Auto-detect**: Omit all flags, auto-detect single session + +#### Priority Order + +When multiple options are provided, they are resolved in this order: + +1. **Explicit flags** (`-S` and `-t`): Highest priority, backward compatible +2. **Session name** (`-s`): Look up in registry +3. **Auto-detect**: If no flags provided and exactly one session exists + +#### New Flags + +| Flag | Description | +|------|-------------| +| `-s, --session ` | Session name (looks up socket/target in registry) | + +All other flags remain the same (see SKILL.md for full reference). + +#### Examples + +**Using session name:** +```bash +./tools/safe-send.sh -s claude-python -c "print('hello')" -w ">>>" +``` + +**Auto-detect single session:** +```bash +./tools/safe-send.sh -c "print('world')" -w ">>>" +``` + +**Explicit (backward compatible):** +```bash +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +./tools/safe-send.sh -S "$SOCKET" -t "my-session:0.0" -c "ls" +``` + +#### Activity Tracking + +When using `-s` flag or auto-detect, the session's `last_active` timestamp is automatically updated. This helps cleanup-sessions.sh make better decisions about session age. + +#### Error Messages + +**Session not found:** +``` +Error: Session 'my-session' not found in registry +Use 'list-sessions.sh' to see available sessions +``` + +**Multiple sessions (auto-detect fails):** +``` +Error: Multiple sessions found (3 total) +Please specify session name with -s or use -t/-S explicitly +Use 'list-sessions.sh' to see available sessions +``` + +**No sessions registered:** +``` +Error: No sessions found in registry +Create a session with 'create-session.sh' or specify -t and -S explicitly +``` + +--- + +## Session Resolution + +### Resolution Algorithm + +The session resolution logic determines which tmux session and socket to use: + +``` +if (socket AND target specified): + use explicit values (backward compatible) +elif (session name specified with -s): + lookup in registry + if not found: + error: session not found + else: + extract socket and target + update last_active timestamp +elif (no flags provided): + count sessions in registry + if count == 1: + auto-use the single session + update last_active timestamp + elif count == 0: + error: no sessions + else: + error: multiple sessions, specify -s +``` + +### Decision Tree + +``` +┌─────────────────────────────┐ +│ Parse command-line flags │ +└──────────┬──────────────────┘ + │ + ▼ + ┌──────────────┐ + │ -S and -t ? │──Yes──► Use explicit socket/target + └──────┬───────┘ (backward compatible) + │ + No + │ + ▼ + ┌──────────────┐ + │ -s flag ? │──Yes──► Look up session in registry + └──────┬───────┘ Update last_active + │ Error if not found + No + │ + ▼ + ┌──────────────────┐ + │ Count sessions │ + └──────┬───────────┘ + │ + ▼ + ┌──────────────────┐ + │ count == 1 ? │──Yes──► Auto-use single session + └──────┬───────────┘ Update last_active + │ + No + │ + ▼ + ┌──────────────────┐ + │ count == 0 ? │──Yes──► Error: No sessions + └──────┬───────────┘ + │ + No (multiple) + │ + ▼ + Error: Multiple sessions, + specify -s or -t/-S +``` + +### Examples + +**Scenario 1: Single session exists** +```bash +# Create one session +./tools/create-session.sh -n my-python --python + +# These all work the same way: +./tools/safe-send.sh -s my-python -c "print(1)" # Explicit name +./tools/safe-send.sh -c "print(1)" # Auto-detect +``` + +**Scenario 2: Multiple sessions exist** +```bash +# Create multiple sessions +./tools/create-session.sh -n python-1 --python +./tools/create-session.sh -n python-2 --python + +# Must specify which one: +./tools/safe-send.sh -s python-1 -c "print(1)" # ✓ Works +./tools/safe-send.sh -c "print(1)" # ✗ Error: multiple sessions +``` + +**Scenario 3: Backward compatibility** +```bash +# Old-style explicit socket/target still works: +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +./tools/safe-send.sh -S "$SOCKET" -t "my-session:0.0" -c "ls" # ✓ Always works +``` + +--- + +## Troubleshooting + +### Common Errors + +#### Error: Session not found in registry + +**Message:** +``` +Error: Session 'my-session' not found in registry +Use 'list-sessions.sh' to see available sessions +``` + +**Causes:** +- Session was never created +- Session was removed from registry +- Typo in session name + +**Solutions:** +```bash +# List all registered sessions +./tools/list-sessions.sh + +# Create the session if it doesn't exist +./tools/create-session.sh -n my-session --python + +# Check for typos in session name (case-sensitive) +``` + +--- + +#### Error: Failed to acquire lock + +**Message:** +``` +registry: Failed to acquire lock after 5s +``` + +**Causes:** +- Another process is performing a registry operation +- Previous lock wasn't released (crash/kill) +- Very slow filesystem (rare) + +**Solutions:** + +**1. Wait and retry** (usually self-resolves): +```bash +# Wait a few seconds and try again +sleep 2 +./tools/safe-send.sh -s my-session -c "command" +``` + +**2. Check for stuck lock:** +```bash +# Check if lock exists +ls -la "$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock"* + +# If flock-based (file): +ls -la "$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock" + +# If mkdir-based (directory): +ls -la "$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock/" +cat "$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock/owner" # See who owns it +``` + +**3. Manual lock removal** (last resort): +```bash +# Remove stuck lock (use with caution!) +rm -rf "$CLAUDE_TMUX_SOCKET_DIR/.sessions.lock" + +# Then retry your operation +``` + +**Prevention:** +- Don't kill registry operations with `kill -9` +- Let operations complete normally +- Increase `LOCK_TIMEOUT` for slow filesystems: + ```bash + export LOCK_TIMEOUT=10 # Increase to 10 seconds + ./tools/safe-send.sh -s my-session -c "command" + ``` + +--- + +#### Error: Multiple sessions found + +**Message:** +``` +Error: Multiple sessions found (3 total) +Please specify session name with -s or use -t/-S explicitly +Use 'list-sessions.sh' to see available sessions +``` + +**Cause:** +- Auto-detect only works when exactly one session exists +- You have multiple registered sessions + +**Solution:** +```bash +# List sessions to see what's registered +./tools/list-sessions.sh + +# Specify which session to use +./tools/safe-send.sh -s specific-session -c "command" + +# Or clean up unused sessions +./tools/cleanup-sessions.sh +``` + +--- + +#### Error: Pane not ready + +**Message:** +``` +Error: Pane not ready (health check failed with exit code 1) +``` + +**Causes:** +- Session crashed or was killed +- Pane is marked as dead by tmux +- Process inside pane exited + +**Solutions:** + +**1. Check session health:** +```bash +./tools/list-sessions.sh +# Look for status: dead, zombie, or missing +``` + +**2. Check pane directly:** +```bash +./tools/pane-health.sh -s my-session +``` + +**3. Recreate session:** +```bash +# Remove dead session from registry +./tools/cleanup-sessions.sh + +# Create fresh session +./tools/create-session.sh -n my-session --python +``` + +**4. Debug manually:** +```bash +# Get session details from registry +./tools/list-sessions.sh --json | jq '.sessions["my-session"]' + +# Try to attach to see what happened +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +tmux -S "$SOCKET" attach -t my-session +``` + +--- + +### Stale Registry Entries + +#### What are stale entries? + +Stale entries occur when: +- Sessions are in the registry but tmux session is gone +- Tmux server was killed without cleanup +- Registry was manually edited incorrectly + +#### How to detect + +**Check health status:** +```bash +./tools/list-sessions.sh +# Look for: dead, missing, zombie, server +``` + +**Check with health tool:** +```bash +./tools/pane-health.sh -s session-name +# Non-zero exit code means unhealthy +``` + +#### How to fix + +**Automatic cleanup (recommended):** +```bash +# Remove all dead/missing/zombie sessions +./tools/cleanup-sessions.sh + +# Preview what would be removed +./tools/cleanup-sessions.sh --dry-run +``` + +**Manual removal:** +```bash +# Remove specific session +cd plugins/tmux/tools +source lib/registry.sh +registry_remove_session "session-name" +``` + +**Nuclear option (clean slate):** +```bash +# Remove ALL sessions from registry +./tools/cleanup-sessions.sh --all + +# Or delete registry file entirely +rm "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" +``` + +#### Prevention + +**Use cleanup tools regularly:** +```bash +# At end of work session +./tools/cleanup-sessions.sh + +# Automated daily cleanup (cron) +0 3 * * * /path/to/tools/cleanup-sessions.sh --older-than 1d +``` + +**Kill sessions properly:** +```bash +# ✓ Good: Kill session, then cleanup registry +tmux -S "$SOCKET" kill-session -t my-session +./tools/cleanup-sessions.sh + +# ✗ Bad: Kill tmux server without cleanup +tmux -S "$SOCKET" kill-server # Leaves stale registry entries +``` + +--- + +### Registry Corruption + +#### What is corruption? + +Registry corruption occurs when `.sessions.json` contains invalid JSON. + +#### How it happens (rare) + +- Disk full during write +- Process crash during write (atomic write-then-move prevents this) +- Manual editing with syntax errors +- Filesystem corruption + +#### How to detect + +**Symptoms:** +- Registry operations fail with jq errors +- Tools report "invalid JSON" errors + +**Verify registry:** +```bash +# Check if registry is valid JSON +jq empty "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" +# Exit code 0 = valid, non-zero = invalid +``` + +#### How to fix + +**1. Backup current registry:** +```bash +cp "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" \ + "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json.backup" +``` + +**2. Try to salvage data:** +```bash +# View the file to see what's wrong +cat "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" + +# Try to fix with jq (if minor issue) +jq '.' "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json.backup" > \ + "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" +``` + +**3. Rebuild registry (if salvage fails):** +```bash +# Remove corrupted registry +rm "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" + +# Registry will be recreated on next operation +./tools/list-sessions.sh +# Shows: No sessions registered + +# Re-register active sessions manually +./tools/create-session.sh -n my-session --python +``` + +**4. Recover from tmux sessions:** +```bash +# List actual tmux sessions +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +tmux -S "$SOCKET" list-sessions + +# Re-register them manually +./tools/create-session.sh -n session-name --shell --no-register +# Note: --no-register prevents duplicate registration check +``` + +#### Prevention + +- **Don't edit `.sessions.json` manually** (use tools instead) +- **Monitor disk space** (atomic writes fail gracefully if disk full) +- **Use the tools** (they validate JSON before writing) +- **Regular backups** (if registry is critical): + ```bash + # Backup registry (cron) + 0 * * * * cp "$CLAUDE_TMUX_SOCKET_DIR/.sessions.json" \ + "$HOME/.sessions.json.$(date +%Y%m%d%H)" + ``` + +--- + +## Migration Guide + +### From Manual Socket Management + +#### Before: Manual Approach + +Typical manual workflow with repetitive boilerplate: + +```bash +# Setup (every session) +SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets +mkdir -p "$SOCKET_DIR" +SOCKET="$SOCKET_DIR/claude.sock" +SESSION="my-python" + +# Create session +tmux -S "$SOCKET" new -d -s "$SESSION" -n shell +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 \ + 'PYTHON_BASIC_REPL=1 python3 -q' Enter + +# Use session (repeat socket/target every time) +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 'print("hello")' Enter +./tools/wait-for-text.sh -S "$SOCKET" -t "$SESSION":0.0 -p '>>>' +tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 'import numpy' Enter +./tools/wait-for-text.sh -S "$SOCKET" -t "$SESSION":0.0 -p '>>>' + +# Cleanup +tmux -S "$SOCKET" kill-session -t "$SESSION" +``` + +**Problems:** +- Repetitive `-S "$SOCKET" -t "$SESSION":0.0` on every command +- Must track socket/session variables across script +- No automatic session discovery +- No built-in health tracking + +#### After: Registry Approach + +Same workflow with ~80% less boilerplate: + +```bash +# Create session (auto-registered) +./tools/create-session.sh -n my-python --python + +# Use session (auto-detects socket/target) +./tools/safe-send.sh -s my-python -c 'print("hello")' -w '>>>' +./tools/safe-send.sh -c 'import numpy' -w '>>>' # Auto-detect single session + +# Cleanup +./tools/cleanup-sessions.sh +``` + +**Benefits:** +- No socket/target repetition +- Auto-detection for single sessions +- Built-in health tracking +- Centralized session management + +#### Step-by-Step Migration + +**1. Install new tools** (already available in `tools/`) +- ✓ `create-session.sh` +- ✓ `list-sessions.sh` +- ✓ `cleanup-sessions.sh` +- ✓ `safe-send.sh` (enhanced) + +**2. Start using registry for new sessions:** +```bash +# Instead of: +# tmux -S "$SOCKET" new -d -s my-session -n shell +# Do: +./tools/create-session.sh -n my-session --shell +``` + +**3. Replace socket/target with session name:** +```bash +# Instead of: +# ./tools/safe-send.sh -S "$SOCKET" -t "$SESSION":0.0 -c "command" +# Do: +./tools/safe-send.sh -s my-session -c "command" +``` + +**4. Use auto-detect for single sessions:** +```bash +# If only one session exists: +./tools/safe-send.sh -c "command" # Omit -s entirely +``` + +**5. Use registry for cleanup:** +```bash +# Instead of: +# tmux -S "$SOCKET" kill-session -t "$SESSION" +# Do: +./tools/cleanup-sessions.sh +``` + +#### Gradual Migration Strategy + +You can use both approaches simultaneously during migration: + +**Phase 1: New sessions only** +- Create new sessions with `create-session.sh` +- Keep existing manual sessions as-is +- Learn the registry workflow + +**Phase 2: Mixed usage** +- Use registry for new sessions +- Continue manual approach for existing sessions +- Both work side-by-side (backward compatible) + +**Phase 3: Full adoption** +- Migrate remaining manual sessions +- Clean up old manual scripts +- Standardize on registry approach + +--- + +## Best Practices + +### When to Use Registry vs Manual + +#### Use Registry When: + +✓ **Interactive development** +- Debugging applications +- Exploring code in REPLs +- Running ad-hoc commands +- Frequent context switching + +✓ **Multi-session workflows** +- Managing multiple Python/gdb sessions +- Switching between different projects +- Parallel debugging tasks + +✓ **Learning and experimentation** +- Testing new tools +- Prototyping workflows +- Educational contexts + +#### Use Manual When: + +✓ **Automation scripts** +- CI/CD pipelines +- Deployment scripts +- Automated testing +- Scripts run by other users + +✓ **Precise control needed** +- Custom socket paths for isolation +- Multiple sessions with same name on different sockets +- Temporary sessions that shouldn't be tracked + +✓ **One-off operations** +- Quick debugging session +- Single command execution +- Script that manages its own cleanup + +#### Hybrid Approach: + +Use `--no-register` flag for one-off sessions: +```bash +# Create session without registering +./tools/create-session.sh -n temp-debug --gdb --no-register + +# Use it manually +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +tmux -S "$SOCKET" send-keys -t temp-debug:0.0 "break main" Enter + +# Kill when done +tmux -S "$SOCKET" kill-session -t temp-debug +``` + +--- + +### Session Naming Conventions + +#### Good Session Names + +✓ **Descriptive and specific:** +```bash +claude-python # Python REPL for Claude agent +debug-api-server # Debugging API server +test-database-queries # Testing database operations +dev-frontend # Frontend development +``` + +✓ **Use hyphens (not spaces):** +```bash +my-python-session # ✓ Good +my_python_session # ✓ OK +my python session # ✗ Bad (spaces cause issues) +``` + +✓ **Keep it short:** +```bash +api-debug # ✓ Good (concise) +debugging-the-new-api # ✗ Too long +``` + +✓ **Use prefixes for grouping:** +```bash +# Project-based prefixes +proj-backend-api +proj-frontend-dev +proj-database-debug + +# Type-based prefixes +python-data-analysis +python-ml-training +gdb-core-dump +gdb-memory-leak + +# Environment prefixes +dev-api-server +test-integration +prod-hotfix-debug +``` + +#### Avoid These Patterns + +✗ **Generic names:** +```bash +session1, session2, test, temp, debug +# Hard to remember what they're for +``` + +✗ **Special characters:** +```bash +my@session, session#1, session.test +# May cause issues with shell parsing +``` + +✗ **Very long names:** +```bash +my-very-long-session-name-for-debugging-the-authentication-module +# Tedious to type, hard to remember +``` + +--- + +### Cleanup Strategies + +#### Manual Cleanup + +**After each work session:** +```bash +# Remove dead sessions +./tools/cleanup-sessions.sh + +# Or preview first +./tools/cleanup-sessions.sh --dry-run +``` + +**When switching projects:** +```bash +# Clean slate for new work +./tools/cleanup-sessions.sh --all +``` + +#### Automated Cleanup + +**Daily cleanup via cron:** +```bash +# Remove sessions older than 1 day (runs at 3am) +0 3 * * * cd /path/to/tmux/tools && ./cleanup-sessions.sh --older-than 1d +``` + +**Weekly cleanup via cron:** +```bash +# Aggressive cleanup once a week (Sunday 2am) +0 2 * * 0 cd /path/to/tmux/tools && ./cleanup-sessions.sh --all +``` + +**Pre-work cleanup script:** +```bash +#!/bin/bash +# ~/bin/start-work.sh + +# Clean up old sessions +cd ~/tmux-skill/tools +./cleanup-sessions.sh --older-than 12h + +# Create fresh sessions for today +./create-session.sh -n work-python --python +./create-session.sh -n work-gdb --gdb + +echo "Work environment ready!" +./list-sessions.sh +``` + +#### Conditional Cleanup + +**In shell scripts:** +```bash +# Cleanup only if more than N sessions exist +session_count=$(./tools/list-sessions.sh --json | jq '.total') +if [[ $session_count -gt 5 ]]; then + echo "Too many sessions ($session_count), cleaning up..." + ./tools/cleanup-sessions.sh --older-than 2h +fi +``` + +**Cleanup based on disk usage:** +```bash +# Cleanup if disk usage high (rare scenario) +disk_usage=$(df /tmp | tail -1 | awk '{print $5}' | sed 's/%//') +if [[ $disk_usage -gt 90 ]]; then + echo "Disk usage high, cleaning up sessions..." + ./tools/cleanup-sessions.sh --all +fi +``` + +#### Best Practices + +✓ **Preview first** (dry-run before aggressive cleanup): +```bash +./tools/cleanup-sessions.sh --dry-run --all +``` + +✓ **Keep recent sessions** (use time-based cleanup): +```bash +./tools/cleanup-sessions.sh --older-than 1d # Keep today's work +``` + +✓ **Document your strategy:** +```bash +# Add comment in crontab +# Cleanup old tmux sessions daily at 3am +0 3 * * * /path/to/cleanup-sessions.sh --older-than 1d +``` + +✗ **Don't cleanup aggressively during active work:** +```bash +# Bad: Cron job every hour during work hours +0 9-17 * * * cleanup-sessions.sh --all # ✗ Will kill active sessions +``` + +--- + +## Advanced Patterns + +### Multiple Sessions on Same Socket + +You can have multiple registered sessions sharing a single socket file: + +```bash +# All use default socket: /tmp/claude-tmux-sockets/claude.sock +./tools/create-session.sh -n python-1 --python +./tools/create-session.sh -n python-2 --python +./tools/create-session.sh -n gdb-debug --gdb + +# Each session has unique target (session:window.pane) +# python-1 → python-1:0.0 +# python-2 → python-2:0.0 +# gdb-debug → gdb-debug:0.0 + +# Use sessions independently +./tools/safe-send.sh -s python-1 -c "print(1)" +./tools/safe-send.sh -s python-2 -c "print(2)" +./tools/safe-send.sh -s gdb-debug -c "break main" + +# List all sessions on default socket +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +tmux -S "$SOCKET" list-sessions +``` + +**Benefits:** +- Easier session discovery (all on one socket) +- Simplified cleanup (kill-server removes all) +- Lower resource usage (one tmux server) + +**When to use:** +- Multiple debugging contexts in one project +- Parallel Python REPLs for different experiments +- Related sessions that should be grouped together + +--- + +### Custom Socket Paths for Isolation + +Use custom sockets to isolate sessions by project or environment: + +```bash +# Project A sessions +./tools/create-session.sh -n proj-a-python \ + -S /tmp/project-a.sock --python +./tools/create-session.sh -n proj-a-gdb \ + -S /tmp/project-a.sock --gdb + +# Project B sessions +./tools/create-session.sh -n proj-b-python \ + -S /tmp/project-b.sock --python + +# Sessions are isolated by socket +./tools/list-sessions.sh +# Shows all sessions across all sockets + +# Can still use session names +./tools/safe-send.sh -s proj-a-python -c "print('Project A')" +./tools/safe-send.sh -s proj-b-python -c "print('Project B')" +``` + +**Benefits:** +- Clean separation between projects +- Can kill entire project's sessions: `tmux -S /tmp/project-a.sock kill-server` +- Prevents naming conflicts across projects +- Easier project-based cleanup + +**When to use:** +- Multiple long-running projects +- Client work requiring isolation +- Different tmux configurations per project + +--- + +### CI/CD Integration + +Use registry for debugging but not for automated pipelines: + +#### Option 1: Don't Register CI Sessions + +```bash +#!/bin/bash +# ci-test.sh - CI/CD test script + +# Create unregistered session +./tools/create-session.sh -n ci-test-$$ \ + --shell --no-register + +# Use explicit socket/target (bypass registry) +SOCKET="/tmp/claude-tmux-sockets/claude.sock" +TARGET="ci-test-$$:0.0" + +# Run tests +./tools/safe-send.sh -S "$SOCKET" -t "$TARGET" \ + -c "pytest tests/" -w "passed" + +# Cleanup (explicit kill, no registry) +tmux -S "$SOCKET" kill-session -t "ci-test-$$" +``` + +**Why:** +- CI sessions are ephemeral +- No need to track them in registry +- Explicit socket/target gives full control + +#### Option 2: Use Registry with Auto-Cleanup + +```bash +#!/bin/bash +# ci-test-with-registry.sh + +# Create registered session +./tools/create-session.sh -n ci-test-$$ --python + +# Use session name +./tools/safe-send.sh -s ci-test-$$ \ + -c "import pytest; pytest.main(['tests/'])" -w "passed" + +# Cleanup via registry +./tools/cleanup-sessions.sh +``` + +**Why:** +- Easier debugging (can inspect sessions) +- Automatic cleanup with registry tools +- Good for hybrid local/CI workflows + +#### Best Practice for CI + +Use environment variable to decide: + +```bash +#!/bin/bash +# Smart CI/local script + +if [[ -n "$CI" ]]; then + # CI: Use explicit approach, no registry + SESSION="ci-test-$$" + ./tools/create-session.sh -n "$SESSION" --python --no-register + SOCKET="/tmp/claude-tmux-sockets/claude.sock" + ./tools/safe-send.sh -S "$SOCKET" -t "$SESSION:0.0" -c "pytest" + tmux -S "$SOCKET" kill-session -t "$SESSION" +else + # Local: Use registry for convenience + ./tools/create-session.sh -n local-test --python + ./tools/safe-send.sh -s local-test -c "pytest" + # Manual cleanup when done +fi +``` + +--- + +## See Also + +- [SKILL.md](../SKILL.md) - Quick reference and common usage +- [tmux man page](https://man.openbsd.org/tmux.1) - Official tmux documentation +- [Implementation Tracker](../../notes/tmux/session-registry-implementation.md) - Development status and roadmap diff --git a/tools/cleanup-sessions.sh b/tools/cleanup-sessions.sh new file mode 100755 index 0000000..15cf64b --- /dev/null +++ b/tools/cleanup-sessions.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +# +# cleanup-sessions.sh - Clean up dead or old tmux sessions +# +# Removes dead sessions from the registry or optionally cleans up +# all sessions or sessions older than a specified threshold. +# +# Usage: +# ./cleanup-sessions.sh [options] +# +# Options: +# --dry-run Show what would be cleaned without doing it +# --all Remove all sessions (even alive ones) +# --older-than Remove sessions older than duration (e.g., "1h", "2d") +# -h, --help Show this help message +# +# Exit codes: +# 0 - Success +# 1 - Invalid arguments + +set -euo pipefail + +# Get script directory to source registry library +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/registry.sh +source "$SCRIPT_DIR/lib/registry.sh" + +#------------------------------------------------------------------------------ +# Configuration +#------------------------------------------------------------------------------ + +dry_run=false +clean_all=false +older_than="" + +#------------------------------------------------------------------------------ +# Functions +#------------------------------------------------------------------------------ + +usage() { + cat << EOF +Usage: $(basename "$0") [options] + +Clean up dead or old tmux sessions from the registry. + +Options: + --dry-run Show what would be cleaned without actually removing + --all Remove all sessions (even alive ones) + --older-than DUR Remove sessions older than duration + -h, --help Show this help message + +Duration Format: + Supported units: s (seconds), m (minutes), h (hours), d (days) + Examples: 30m, 2h, 1d, 3600s + +Cleanup Modes: + Default: Remove only dead/missing/zombie sessions + --all: Remove all sessions regardless of health + --older-than: Remove sessions older than specified duration + +Examples: + # Show what dead sessions would be removed (dry-run) + $(basename "$0") --dry-run + + # Remove all dead sessions + $(basename "$0") + + # Remove sessions inactive for more than 1 hour + $(basename "$0") --older-than 1h + + # Remove all sessions (even alive ones) + $(basename "$0") --all + + # Dry-run: show sessions older than 2 days + $(basename "$0") --dry-run --older-than 2d + +Exit codes: + 0 - Success + 1 - Invalid arguments +EOF +} + +# Parse duration string to seconds +# Args: duration string (e.g., "1h", "30m", "2d") +# Returns: seconds as integer +parse_duration() { + local dur="$1" + + if [[ "$dur" =~ ^([0-9]+)([smhd])$ ]]; then + local value="${BASH_REMATCH[1]}" + local unit="${BASH_REMATCH[2]}" + + case "$unit" in + s) echo "$value" ;; + m) echo "$((value * 60))" ;; + h) echo "$((value * 3600))" ;; + d) echo "$((value * 86400))" ;; + esac + else + echo "Error: Invalid duration format: $dur" >&2 + echo "Use format like: 30m, 2h, 1d" >&2 + return 1 + fi +} + +# Check if session is older than threshold +# Args: created_at timestamp, threshold in seconds +# Returns: 0 if older, 1 if newer +is_older_than() { + local created="$1" + local threshold_secs="$2" + + # Convert ISO8601 to epoch (cross-platform) + local created_epoch + created_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$created" "+%s" 2>/dev/null || \ + date -d "$created" "+%s" 2>/dev/null || echo "0") + + if [[ "$created_epoch" == "0" ]]; then + # Can't parse date, assume it's old + return 0 + fi + + local now_epoch + now_epoch=$(date "+%s") + local age=$((now_epoch - created_epoch)) + + [[ $age -gt $threshold_secs ]] +} + +#------------------------------------------------------------------------------ +# Argument parsing +#------------------------------------------------------------------------------ + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + dry_run=true + shift + ;; + --all) + clean_all=true + shift + ;; + --older-than) + older_than="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Error: Unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +#------------------------------------------------------------------------------ +# Validate arguments +#------------------------------------------------------------------------------ + +threshold_secs=0 +if [[ -n "$older_than" ]]; then + threshold_secs=$(parse_duration "$older_than") || exit 1 +fi + +#------------------------------------------------------------------------------ +# Get sessions from registry +#------------------------------------------------------------------------------ + +registry_data=$(registry_list_sessions) +session_names=$(echo "$registry_data" | jq -r '.sessions | keys[]' 2>/dev/null || echo "") + +if [[ -z "$session_names" ]]; then + echo "No sessions in registry." + exit 0 +fi + +#------------------------------------------------------------------------------ +# Find sessions to remove +#------------------------------------------------------------------------------ + +# Path to pane-health tool +PANE_HEALTH="$SCRIPT_DIR/pane-health.sh" + +sessions_to_remove=() +removed_count=0 + +while IFS= read -r name; do + [[ -z "$name" ]] && continue + + # Get session data + socket=$(echo "$registry_data" | jq -r ".sessions[\"$name\"].socket") + target=$(echo "$registry_data" | jq -r ".sessions[\"$name\"].target") + created=$(echo "$registry_data" | jq -r ".sessions[\"$name\"].created_at") + + # Determine if session should be removed + should_remove=false + reason="" + + if [[ "$clean_all" == true ]]; then + should_remove=true + reason="all sessions mode" + else + # Check health status + if [[ -x "$PANE_HEALTH" ]]; then + if ! "$PANE_HEALTH" -S "$socket" -t "$target" --format text >/dev/null 2>&1; then + should_remove=true + reason="dead/missing/zombie" + fi + fi + + # Check age if threshold specified + if [[ -n "$older_than" ]] && [[ "$threshold_secs" -gt 0 ]]; then + if is_older_than "$created" "$threshold_secs"; then + should_remove=true + if [[ -n "$reason" ]]; then + reason="$reason + older than $older_than" + else + reason="older than $older_than" + fi + fi + fi + fi + + # Add to removal list if needed + if [[ "$should_remove" == true ]]; then + sessions_to_remove+=("$name|$reason") + fi +done <<< "$session_names" + +#------------------------------------------------------------------------------ +# Remove sessions +#------------------------------------------------------------------------------ + +if [[ ${#sessions_to_remove[@]} -eq 0 ]]; then + echo "No sessions to clean up." + exit 0 +fi + +if [[ "$dry_run" == true ]]; then + echo "Dry-run mode: Would remove ${#sessions_to_remove[@]} session(s):" + for session_info in "${sessions_to_remove[@]}"; do + IFS='|' read -r name reason <<< "$session_info" + echo " - $name ($reason)" + done +else + echo "Removing ${#sessions_to_remove[@]} session(s):" + for session_info in "${sessions_to_remove[@]}"; do + IFS='|' read -r name reason <<< "$session_info" + echo " - $name ($reason)" + if registry_remove_session "$name"; then + removed_count=$((removed_count + 1)) + else + echo " Warning: Failed to remove $name" >&2 + fi + done + echo "Removed $removed_count session(s) successfully." +fi + +exit 0 diff --git a/tools/create-session.sh b/tools/create-session.sh new file mode 100755 index 0000000..a3239ed --- /dev/null +++ b/tools/create-session.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +# +# create-session.sh - Create and register tmux sessions +# +# Creates a new tmux session and optionally registers it in the session registry. +# Supports launching different types of sessions (Python REPL, gdb, shell). +# +# Usage: +# ./create-session.sh -n [options] +# +# Options: +# -n, --name Session name (required) +# -S, --socket Custom socket path (optional, uses default) +# -w, --window Window name (default: "shell") +# --python Launch Python REPL with PYTHON_BASIC_REPL=1 +# --gdb Launch gdb +# --shell Launch shell (default) +# --no-register Don't add to registry +# -h, --help Show this help message +# +# Exit codes: +# 0 - Success +# 1 - Invalid arguments +# 2 - Session already exists +# 3 - Tmux command failed +# 4 - Registry operation failed + +set -euo pipefail + +# Get script directory to source registry library +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/registry.sh +source "$SCRIPT_DIR/lib/registry.sh" + +#------------------------------------------------------------------------------ +# Configuration +#------------------------------------------------------------------------------ + +# Defaults +session_name="" +socket="" +window_name="shell" +session_type="shell" +launch_command="bash" +register_session=true + +#------------------------------------------------------------------------------ +# Functions +#------------------------------------------------------------------------------ + +usage() { + cat << EOF +Usage: $(basename "$0") -n [options] + +Create a new tmux session and optionally register it in the session registry. + +Options: + -n, --name Session name (required) + -S, --socket Custom socket path (optional, uses default) + -w, --window Window name (default: "shell") + --python Launch Python REPL with PYTHON_BASIC_REPL=1 + --gdb Launch gdb + --shell Launch shell (default) + --no-register Don't add to registry + -h, --help Show this help message + +Session Types: + --shell Launches bash (default) + --python Launches Python REPL with PYTHON_BASIC_REPL=1 + --gdb Launches gdb debugger + +Examples: + # Create Python REPL session (auto-registered) + $(basename "$0") -n my-python --python + + # Create session with custom socket + $(basename "$0") -n my-session -S /tmp/custom.sock --shell + + # Create session without registering + $(basename "$0") -n temp-session --no-register + +Exit codes: + 0 - Success + 1 - Invalid arguments + 2 - Session already exists + 3 - Tmux command failed + 4 - Registry operation failed +EOF +} + +#------------------------------------------------------------------------------ +# Argument parsing +#------------------------------------------------------------------------------ + +if [[ $# -eq 0 ]]; then + usage + exit 1 +fi + +while [[ $# -gt 0 ]]; do + case "$1" in + -n|--name) + session_name="$2" + shift 2 + ;; + -S|--socket) + socket="$2" + shift 2 + ;; + -w|--window) + window_name="$2" + shift 2 + ;; + --python) + session_type="python-repl" + launch_command="PYTHON_BASIC_REPL=1 python3 -q" + shift + ;; + --gdb) + session_type="debugger" + launch_command="gdb" + shift + ;; + --shell) + session_type="shell" + launch_command="bash" + shift + ;; + --no-register) + register_session=false + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Error: Unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +#------------------------------------------------------------------------------ +# Validation +#------------------------------------------------------------------------------ + +if [[ -z "$session_name" ]]; then + echo "Error: Session name is required (use -n )" >&2 + usage + exit 1 +fi + +# Set default socket if not provided +if [[ -z "$socket" ]]; then + socket="$CLAUDE_TMUX_SOCKET_DIR/claude.sock" +fi + +# Ensure socket directory exists +socket_dir="$(dirname "$socket")" +mkdir -p "$socket_dir" + +#------------------------------------------------------------------------------ +# Check if session already exists +#------------------------------------------------------------------------------ + +# Check in registry first +if [[ "$register_session" == true ]] && registry_session_exists "$session_name"; then + echo "Error: Session '$session_name' already exists in registry" >&2 + echo "Use a different name or remove the existing session first" >&2 + exit 2 +fi + +# Check if tmux session actually exists on this socket +if tmux -S "$socket" has-session -t "$session_name" 2>/dev/null; then + echo "Error: Tmux session '$session_name' already exists on socket $socket" >&2 + echo "Use a different name or kill the existing session first" >&2 + exit 2 +fi + +#------------------------------------------------------------------------------ +# Create tmux session +#------------------------------------------------------------------------------ + +if ! tmux -S "$socket" new-session -d -s "$session_name" -n "$window_name" "$launch_command" 2>/dev/null; then + echo "Error: Failed to create tmux session '$session_name'" >&2 + exit 3 +fi + +# Get the session PID +session_pid=$(tmux -S "$socket" display-message -p -t "$session_name" '#{pane_pid}' 2>/dev/null || echo "") + +# Build target (session:window.pane) +target="$session_name:0.0" + +#------------------------------------------------------------------------------ +# Register session +#------------------------------------------------------------------------------ + +if [[ "$register_session" == true ]]; then + if ! registry_add_session "$session_name" "$socket" "$target" "$session_type" "$session_pid"; then + echo "Warning: Session created but failed to register in registry" >&2 + # Don't fail completely, session was created successfully + fi +fi + +#------------------------------------------------------------------------------ +# Output session info as JSON +#------------------------------------------------------------------------------ + +cat << EOF +{ + "name": "$session_name", + "socket": "$socket", + "target": "$target", + "type": "$session_type", + "pid": ${session_pid:-null}, + "window": "$window_name", + "registered": $register_session +} +EOF + +exit 0 diff --git a/tools/find-sessions.sh b/tools/find-sessions.sh new file mode 100755 index 0000000..bda363a --- /dev/null +++ b/tools/find-sessions.sh @@ -0,0 +1,262 @@ +#!/usr/bin/env bash +# +# find-sessions.sh - Discover and list tmux sessions on sockets +# +# PURPOSE: +# Find and display information about tmux sessions, either on a specific +# socket or by scanning all sockets in a directory. Useful for discovering +# what agent sessions are currently running. +# +# HOW IT WORKS: +# 1. Identify target socket(s) based on command-line options +# 2. Query each socket for running tmux sessions +# 3. Display session info: name, attach status, creation time +# 4. Optionally filter by session name substring +# +# USE CASES: +# - List all agent sessions across multiple sockets +# - Find a specific session by name (partial matching) +# - Check if a session is attached or detached +# - See when sessions were created +# - Enumerate sessions before cleanup +# +# EXAMPLES: +# # List sessions on default tmux socket +# ./find-sessions.sh +# +# # List sessions on specific socket by name +# ./find-sessions.sh -L mysocket +# +# # List sessions on specific socket by path +# ./find-sessions.sh -S /tmp/claude-tmux-sockets/claude.sock +# +# # Scan all sockets in directory +# ./find-sessions.sh --all +# +# # Find sessions with "python" in the name +# ./find-sessions.sh --all -q python +# +# DEPENDENCIES: +# - bash (with arrays, [[, functions) +# - tmux (for list-sessions) +# - grep (for filtering by query) +# + +# Bash strict mode: +# -e: Exit immediately if any command fails +# -u: Treat unset variables as errors +# -o pipefail: Pipe fails if any command in pipeline fails +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: find-sessions.sh [-L socket-name|-S socket-path|-A] [-q pattern] + +List tmux sessions on a socket (default tmux socket if none provided). + +Options: + -L, --socket tmux socket name (passed to tmux -L) + -S, --socket-path tmux socket path (passed to tmux -S) + -A, --all scan all sockets under CLAUDE_TMUX_SOCKET_DIR + -q, --query case-insensitive substring to filter session names + -h, --help show this help +USAGE +} + +# ============================================================================ +# Default Configuration +# ============================================================================ + +# Socket specification (mutually exclusive) +socket_name="" # tmux socket name (for tmux -L) +socket_path="" # tmux socket path (for tmux -S) + +# Filtering and scanning options +query="" # substring to filter session names (case-insensitive) +scan_all=false # whether to scan all sockets in socket_dir + +# Directory containing agent tmux sockets +# Priority: CLAUDE_TMUX_SOCKET_DIR env var > TMPDIR/claude-tmux-sockets > /tmp/claude-tmux-sockets +socket_dir="${CLAUDE_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/claude-tmux-sockets}" + +# ============================================================================ +# Parse Command-Line Arguments +# ============================================================================ + +while [[ $# -gt 0 ]]; do + case "$1" in + -L|--socket) socket_name="${2-}"; shift 2 ;; # Socket name mode + -S|--socket-path) socket_path="${2-}"; shift 2 ;; # Socket path mode + -A|--all) scan_all=true; shift ;; # Scan all mode + -q|--query) query="${2-}"; shift 2 ;; # Filter by name + -h|--help) usage; exit 0 ;; # Show help + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; # Error on unknown + esac +done + +# ============================================================================ +# Validate Options +# ============================================================================ + +# Cannot use --all with specific socket options (they're mutually exclusive) +if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then + echo "Cannot combine --all with -L or -S" >&2 + exit 1 +fi + +# Cannot use both -L and -S at the same time (different socket types) +if [[ -n "$socket_name" && -n "$socket_path" ]]; then + echo "Use either -L or -S, not both" >&2 + exit 1 +fi + +# Check that tmux is installed and available in PATH +if ! command -v tmux >/dev/null 2>&1; then + echo "tmux not found in PATH" >&2 + exit 1 +fi + +# ============================================================================ +# Function: list_sessions +# ============================================================================ +# Query a tmux socket for sessions and display formatted output +# +# Arguments: +# $1: Label describing the socket (for display purposes) +# $@: Remaining args are passed to tmux command (e.g., -L name or -S path) +# +# Returns: +# 0 if sessions found (or no sessions after filtering) +# 1 if tmux server not running on this socket +# +# Output format: +# Sessions on