From cb5e07c082f09f14b7a84bc6bccf5ed9038a3af1 Mon Sep 17 00:00:00 2001 From: morecup Date: Sat, 14 Feb 2026 05:16:21 +0000 Subject: [PATCH 1/2] fix(tmux): fix 4 bugs in spawnInTmux and executeTmuxCommand 1. Move -P -F #{pane_pid} before the shell command (tmux requires flags before the command argument in new-window) 2. Skip adding -t target in executeTmuxCommand when -t is already present in the command args (prevents duplicate -t flags) 3. Remove shell-style quoting from -e KEY=value (spawn bypasses shell, so quotes become literal characters in the value) 4. Prefix command with 'exec' so the shell is replaced by the actual process, making pane_pid match the real process PID --- src/utils/tmux.ts | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/utils/tmux.ts b/src/utils/tmux.ts index f0958358..445d21b0 100644 --- a/src/utils/tmux.ts +++ b/src/utils/tmux.ts @@ -448,7 +448,8 @@ export class TmuxUtilities { const fullCmd = [...baseCmd, ...cmd]; // Add target specification for commands that support it - if (cmd.length > 0 && COMMANDS_SUPPORTING_TARGET.has(cmd[0])) { + // Skip if -t is already present in the command args + if (cmd.length > 0 && COMMANDS_SUPPORTING_TARGET.has(cmd[0]) && !cmd.includes('-t')) { let target = targetSession; if (window) target += `:${window}`; if (pane) target += `.${pane}`; @@ -782,7 +783,8 @@ export class TmuxUtilities { await this.ensureSessionExists(sessionName); // Build command to execute in the new window - const fullCommand = args.join(' '); + // Use exec to replace the shell process so pane_pid matches the actual process PID + const fullCommand = 'exec ' + args.join(' '); // Create new window in session with command and environment variables // IMPORTANT: Don't manually add -t here - executeTmuxCommand handles it via parameters @@ -811,26 +813,19 @@ export class TmuxUtilities { continue; } - // Escape value for shell safety - // Must escape: backslashes, double quotes, dollar signs, backticks - const escapedValue = value - .replace(/\\/g, '\\\\') // Backslash first! - .replace(/"/g, '\\"') // Double quotes - .replace(/\$/g, '\\$') // Dollar signs - .replace(/`/g, '\\`'); // Backticks - - createWindowArgs.push('-e', `${key}="${escapedValue}"`); + // No shell escaping needed - spawn passes args directly to exec + createWindowArgs.push('-e', `${key}=${value}`); } logger.debug(`[TMUX] Setting ${Object.keys(env).length} environment variables in tmux window`); } - // Add the command to run in the window (runs immediately when window is created) - createWindowArgs.push(fullCommand); - - // Add -P flag to print the pane PID immediately + // Add -P flag to print the pane PID immediately (must come before the command) createWindowArgs.push('-P'); createWindowArgs.push('-F', '#{pane_pid}'); + // Add the command to run in the window (runs immediately when window is created) + createWindowArgs.push(fullCommand); + // Create window with command and get PID immediately const createResult = await this.executeTmuxCommand(createWindowArgs, sessionName); From 318751dfd9e0420c234654e8adf776d09422d8a1 Mon Sep 17 00:00:00 2001 From: morecup Date: Sat, 14 Feb 2026 12:28:03 +0000 Subject: [PATCH 2/2] fix: tmux spawn -t flag placement and webhook timeout - Fix -t session flag being appended after shell command in new-window, causing tmux to pass it as part of the command string (process crashes) - Move -t before shell command arg and skip auto-append in executeTmuxCommand - Increase webhook timeout from 15s to 60s for slow MCP/project init --- src/daemon/run.ts | 7 +++---- src/utils/tmux.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/daemon/run.ts b/src/daemon/run.ts index 75889d14..4524514a 100644 --- a/src/daemon/run.ts +++ b/src/daemon/run.ts @@ -448,7 +448,7 @@ export async function startDaemon(): Promise { type: 'error', errorMessage: `Session webhook timeout for PID ${tmuxResult.pid} (tmux)` }); - }, 15_000); // Same timeout as regular sessions + }, 60_000); // Extended timeout for slow MCP/project init // Register awaiter for tmux session (exact same as regular flow) pidToAwaiter.set(tmuxResult.pid!, (completedSession) => { @@ -563,9 +563,8 @@ export async function startDaemon(): Promise { type: 'error', errorMessage: `Session webhook timeout for PID ${happyProcess.pid}` }); - // 15 second timeout - I have seen timeouts on 10 seconds - // even though session was still created successfully in ~2 more seconds - }, 15_000); + // 60 second timeout - MCP servers and project init can be slow + }, 60_000); // Register awaiter pidToAwaiter.set(happyProcess.pid!, (completedSession) => { diff --git a/src/utils/tmux.ts b/src/utils/tmux.ts index 445d21b0..cd447cc1 100644 --- a/src/utils/tmux.ts +++ b/src/utils/tmux.ts @@ -823,11 +823,16 @@ export class TmuxUtilities { createWindowArgs.push('-P'); createWindowArgs.push('-F', '#{pane_pid}'); - // Add the command to run in the window (runs immediately when window is created) + // Add -t flag BEFORE the shell command (tmux requires all flags before the command arg) + createWindowArgs.push('-t', sessionName); + + // Add the command to run in the window (MUST be last argument) createWindowArgs.push(fullCommand); // Create window with command and get PID immediately - const createResult = await this.executeTmuxCommand(createWindowArgs, sessionName); + logger.debug(`[TMUX] Full command args count: ${createWindowArgs.length}, session: ${sessionName}`); + // Pass NO session to executeTmuxCommand so it won't append -t again + const createResult = await this.executeTmuxCommand(createWindowArgs); if (!createResult || createResult.returncode !== 0) { throw new Error(`Failed to create tmux window: ${createResult?.stderr}`);