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
This commit is contained in:
Codex Review
2026-04-18 08:47:00 +00:00
parent 8eabb2be16
commit 4975462039
3 changed files with 18 additions and 2 deletions

View File

@@ -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 {

View File

@@ -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 });
});
});

View File

@@ -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}`);