From 7bed751db000e43319dd100a441e88a1dfe7017c Mon Sep 17 00:00:00 2001 From: Helbetica <102046328+helbetica-com-ar@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:47:46 -0300 Subject: [PATCH] fix: auto-start dev servers in tmux instead of blocking (#344) * fix: auto-start development servers in tmux instead of blocking Replace blocking PreToolUse hook that used process.exit(2) with an auto-transform hook that: - Detects development server commands - Wraps them in tmux with directory-based session names - Runs server detached so Claude Code is not blocked - Provides confirmation message with log viewing instructions Benefits: - Development servers no longer block Claude Code execution - Each project gets its own tmux session (allows multiple projects) - Logs remain accessible via 'tmux capture-pane -t ' - Non-blocking: if tmux unavailable, command still runs (graceful fallback) Implementation: - Created scripts/hooks/auto-tmux-dev.js with transform logic - Updated hooks.json to reference the script instead of inline node command - Applied same fix to cached plugin version (1.4.1) for immediate effect * fix: resolve PR #344 code review issues in auto-tmux-dev.js Critical fixes: - Fix variable scope: declare 'input' before try block, not inside - Fix shell injection: sanitize sessionName and escape cmd for shell - Replace unused execFileSync import with spawnSync Improvements: - Add real Windows support using cmd /k window launcher - Add tmux availability check with graceful fallback - Update header comment to accurately describe platform support Test coverage: - Valid JSON input: transforms command for respective platform - Invalid JSON: passes through raw data unchanged - Unsupported tools: gracefully falls back to original command - Shell metacharacters: sanitized in sessionName, escaped in cmd * fix: correct cmd.exe escape sequence for double quotes on Windows Use double-quote doubling ('""') instead of backslash-escape ('\\\") for cmd.exe syntax. Backslash escaping is Unix convention and not recognized by cmd.exe. This fixes quoted arguments in dev server commands on Windows (e.g., 'npm run dev --filter="my-app"'). --- hooks/hooks.json | 4 +- scripts/hooks/auto-tmux-dev.js | 88 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100755 scripts/hooks/auto-tmux-dev.js diff --git a/hooks/hooks.json b/hooks/hooks.json index 5d6c3648..342b5b81 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -7,10 +7,10 @@ "hooks": [ { "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:bash:dev-server-block\" \"scripts/hooks/pre-bash-dev-server-block.js\" \"standard,strict\"" + "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/auto-tmux-dev.js\"" } ], - "description": "Block dev servers outside tmux - ensures you can access logs" + "description": "Auto-start dev servers in tmux with directory-based session names" }, { "matcher": "Bash", diff --git a/scripts/hooks/auto-tmux-dev.js b/scripts/hooks/auto-tmux-dev.js new file mode 100755 index 00000000..b3a561a8 --- /dev/null +++ b/scripts/hooks/auto-tmux-dev.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node +/** + * Auto-Tmux Dev Hook - Start dev servers in tmux/cmd automatically + * + * macOS/Linux: Runs dev server in a named tmux session (non-blocking). + * Falls back to original command if tmux is not installed. + * Windows: Opens dev server in a new cmd window (non-blocking). + * + * Runs before Bash tool use. If command is a dev server (npm run dev, pnpm dev, yarn dev, bun run dev), + * transforms it to run in a detached session. + * + * Benefits: + * - Dev server runs detached (doesn't block Claude Code) + * - Session persists (can run `tmux capture-pane -t -p` to see logs on Unix) + * - Session name matches project directory (allows multiple projects simultaneously) + * + * Session management (Unix): + * - Checks tmux availability before transforming + * - Kills any existing session with the same name (clean restart) + * - Creates new detached session + * - Reports session name and how to view logs + * + * Session management (Windows): + * - Opens new cmd window with descriptive title + * - Allows multiple dev servers to run simultaneously + */ + +const path = require('path'); +const { spawnSync } = require('child_process'); + +const MAX_STDIN = 1024 * 1024; // 1MB limit +let data = ''; +process.stdin.setEncoding('utf8'); + +process.stdin.on('data', chunk => { + if (data.length < MAX_STDIN) { + const remaining = MAX_STDIN - data.length; + data += chunk.substring(0, remaining); + } +}); + +process.stdin.on('end', () => { + let input; + try { + input = JSON.parse(data); + const cmd = input.tool_input?.command || ''; + + // Detect dev server commands: npm run dev, pnpm dev, yarn dev, bun run dev + // Use word boundary (\b) to avoid matching partial commands + const devServerRegex = /(npm run dev\b|pnpm( run)? dev\b|yarn dev\b|bun run dev\b)/; + + if (devServerRegex.test(cmd)) { + // Get session name from current directory basename, sanitize for shell safety + // e.g., /home/user/Portfolio → "Portfolio", /home/user/my-app-v2 → "my-app-v2" + const rawName = path.basename(process.cwd()); + // Replace non-alphanumeric characters (except - and _) with underscore to prevent shell injection + const sessionName = rawName.replace(/[^a-zA-Z0-9_-]/g, '_') || 'dev'; + + if (process.platform === 'win32') { + // Windows: open in a new cmd window (non-blocking) + // Escape double quotes in cmd for cmd /k syntax + const escapedCmd = cmd.replace(/"/g, '""'); + input.tool_input.command = `start "DevServer-${sessionName}" cmd /k "${escapedCmd}"`; + } else { + // Unix (macOS/Linux): Check tmux is available before transforming + const tmuxCheck = spawnSync('which', ['tmux'], { encoding: 'utf8' }); + if (tmuxCheck.status === 0) { + // Escape single quotes for shell safety: 'text' -> 'text'\''text' + const escapedCmd = cmd.replace(/'/g, "'\\''"); + + // Build the transformed command: + // 1. Kill existing session (silent if doesn't exist) + // 2. Create new detached session with the dev command + // 3. Echo confirmation message with instructions for viewing logs + const transformedCmd = `SESSION="${sessionName}"; tmux kill-session -t "$SESSION" 2>/dev/null || true; tmux new-session -d -s "$SESSION" '${escapedCmd}' && echo "[Hook] Dev server started in tmux session '${sessionName}'. View logs: tmux capture-pane -t ${sessionName} -p -S -100"`; + + input.tool_input.command = transformedCmd; + } + // else: tmux not found, pass through original command unchanged + } + } + process.stdout.write(JSON.stringify(input)); + } catch { + // Invalid input — pass through original data unchanged + process.stdout.write(data); + } + process.exit(0); +});