mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
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 <session>' - 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"').
This commit is contained in:
@@ -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",
|
||||
|
||||
88
scripts/hooks/auto-tmux-dev.js
Executable file
88
scripts/hooks/auto-tmux-dev.js
Executable file
@@ -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 <session> -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);
|
||||
});
|
||||
Reference in New Issue
Block a user