From 49754620392f8763ef5b3eea42f154a74a5ae30f Mon Sep 17 00:00:00 2001 From: Codex Review Date: Sat, 18 Apr 2026 08:47:00 +0000 Subject: [PATCH] Preserve POSIX tmux preflight on MSYS2 Windows shells The first #2711 fix correctly stopped native Windows shells from receiving Unix-only exec wrapping, but the preflight path in runClaudeOutsideTmux still keyed off process.platform alone. That caused MSYS2/Git Bash sessions on Windows to skip the existing POSIX sleep/tcflush preflight even though they still use login-shell wrapping. Constraint: Keep native Windows COMSPEC handling intact Constraint: Preserve existing POSIX/MSYS2 login-shell behavior without broad launch refactors Rejected: Revert the Windows-specific path entirely | would reintroduce the original PowerShell failure Rejected: Add separate MSYS2-specific launch branches everywhere | broader drift than needed Confidence: high Scope-risk: narrow Reversibility: clean Directive: When branching shell launch behavior, key off native-Windows-vs-POSIX shell semantics, not process.platform by itself Tested: npm test -- --run src/cli/__tests__/tmux-utils.test.ts src/cli/__tests__/launch.test.ts src/cli/__tests__/autoresearch-guided.test.ts Tested: npm run build:cli Not-tested: Live tmux launch on a real MSYS2/Git Bash Windows host Related: #2711 Related: #2713 --- bridge/cli.cjs | 2 +- src/cli/__tests__/launch.test.ts | 16 ++++++++++++++++ src/cli/launch.ts | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bridge/cli.cjs b/bridge/cli.cjs index 57027aa3..e15871f8 100755 --- a/bridge/cli.cjs +++ b/bridge/cli.cjs @@ -85582,7 +85582,7 @@ function runClaudeOutsideTmux(cwd2, args, _sessionId) { ); const rawClaudeCmd = isNativeWindowsShell() ? buildTmuxShellCommandWithEnv("claude", args, forwardedEnv) : buildTmuxShellCommand("claude", args); const envPrefix = !isNativeWindowsShell() && Object.keys(forwardedEnv).length > 0 ? buildEnvExportPrefix(TMUX_ENV_FORWARD) : ""; - const preflight = process.platform === "win32" ? envPrefix : `${envPrefix}sleep 0.3; perl -e 'use POSIX;tcflush(0,TCIFLUSH)' 2>/dev/null; `; + const preflight = isNativeWindowsShell() ? envPrefix : `${envPrefix}sleep 0.3; perl -e 'use POSIX;tcflush(0,TCIFLUSH)' 2>/dev/null; `; const claudeCmd = wrapWithLoginShell(`${preflight}${rawClaudeCmd}`); const sessionName2 = buildTmuxSessionName(cwd2); try { diff --git a/src/cli/__tests__/launch.test.ts b/src/cli/__tests__/launch.test.ts index bfe437eb..0caccacc 100644 --- a/src/cli/__tests__/launch.test.ts +++ b/src/cli/__tests__/launch.test.ts @@ -1245,4 +1245,20 @@ describe('runClaude outside-tmux — env forwarding', () => { Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); }); + + it('keeps POSIX preflight commands on MSYS2 Windows shells', () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { value: 'win32', configurable: true }); + process.env.CLAUDE_CONFIG_DIR = '/custom/config'; + vi.mocked(isNativeWindowsShell).mockReturnValue(false); + + runClaude('/tmp', ['--print-system-prompt', 'hello world'], 'sid'); + + const rawCommand = vi.mocked(wrapWithLoginShell).mock.calls[0][0]; + expect(rawCommand).toContain('export CLAUDE_CONFIG_DIR=/custom/config'); + expect(rawCommand).toContain('sleep 0.3'); + expect(rawCommand).toContain("perl -e 'use POSIX;tcflush(0,TCIFLUSH)' 2>/dev/null;"); + + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + }); }); diff --git a/src/cli/launch.ts b/src/cli/launch.ts index efd43524..4e53664d 100644 --- a/src/cli/launch.ts +++ b/src/cli/launch.ts @@ -476,7 +476,7 @@ function runClaudeOutsideTmux(cwd: string, args: string[], _sessionId: string): // A short sleep lets the response arrive, then tcflush discards it. // Wrap in login shell so .bashrc/.zshrc are sourced (PATH, nvm, etc.) // Env exports are injected after RC sourcing so they override stale tmux server env. - const preflight = process.platform === 'win32' + const preflight = isNativeWindowsShell() ? envPrefix : `${envPrefix}sleep 0.3; perl -e 'use POSIX;tcflush(0,TCIFLUSH)' 2>/dev/null; `; const claudeCmd = wrapWithLoginShell(`${preflight}${rawClaudeCmd}`);