From 10d75852bbd81543117177d78667111e4098f642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Tue, 19 May 2026 23:43:08 +0800 Subject: [PATCH] fix: Preserve direct TUI input in native terminals Direct native CLI launches already inherit their user's shell environment, so recapturing a login interactive shell from the same TTY is unnecessary and can interfere with prompt input on macOS terminals. The desktop and non-interactive subprocess paths still keep the shell-env bridge because they do not own the user's interactive terminal. Constraint: Windows was already excluded from terminal shell env capture, which explains why v0.2.8 remained healthy there. Rejected: Disable terminal shell env capture globally | desktop subprocesses still need the captured login-shell environment. Confidence: high Scope-risk: narrow Directive: Do not re-enable interactive shell env capture for direct TTY launches without a native PTY input regression test. Tested: bun test src/utils/terminalShellEnvironment.test.ts src/utils/mcpStdioEnvironment.test.ts Tested: Native PTY smoke confirmed typed characters appear in claude-haha prompt Tested: SKIP_INSTALL=1 ./desktop/scripts/build-macos-arm64.sh Not-tested: Full bun run verify was intentionally not completed after user requested only a local macOS build --- src/utils/terminalShellEnvironment.test.ts | 38 ++++++++++++++++++++++ src/utils/terminalShellEnvironment.ts | 5 ++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/utils/terminalShellEnvironment.test.ts b/src/utils/terminalShellEnvironment.test.ts index c7b52b61..fdf39e27 100644 --- a/src/utils/terminalShellEnvironment.test.ts +++ b/src/utils/terminalShellEnvironment.test.ts @@ -101,4 +101,42 @@ describe('terminal shell environment', () => { expect(env).toBeNull() }) + + it('does not capture shell env when the current process owns an interactive TTY', async () => { + const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, 'isTTY') + const stdoutDescriptor = Object.getOwnPropertyDescriptor(process.stdout, 'isTTY') + + Object.defineProperty(process.stdin, 'isTTY', { + configurable: true, + value: true, + }) + Object.defineProperty(process.stdout, 'isTTY', { + configurable: true, + value: true, + }) + + try { + const shellPath = path.join(tmpDir, 'zsh') + await writeFakeZsh(shellPath) + + const env = await getTerminalShellEnvironment({ + HOME: tmpDir, + SHELL: shellPath, + PATH: '/usr/bin:/bin', + }) + + expect(env).toBeNull() + } finally { + if (stdinDescriptor) { + Object.defineProperty(process.stdin, 'isTTY', stdinDescriptor) + } else { + delete (process.stdin as { isTTY?: boolean }).isTTY + } + if (stdoutDescriptor) { + Object.defineProperty(process.stdout, 'isTTY', stdoutDescriptor) + } else { + delete (process.stdout as { isTTY?: boolean }).isTTY + } + } + }) }) diff --git a/src/utils/terminalShellEnvironment.ts b/src/utils/terminalShellEnvironment.ts index f13367a8..8ee4b629 100644 --- a/src/utils/terminalShellEnvironment.ts +++ b/src/utils/terminalShellEnvironment.ts @@ -104,7 +104,10 @@ async function captureTerminalShellEnvironment( ): Promise | null> { if ( process.platform === 'win32' || - isEnvTruthy(baseEnv.CC_HAHA_DISABLE_TERMINAL_SHELL_ENV) + isEnvTruthy(baseEnv.CC_HAHA_DISABLE_TERMINAL_SHELL_ENV) || + // Direct terminal launches already inherit shell env; spawning an + // interactive login shell here can interfere with the active TTY. + (process.stdin.isTTY && process.stdout.isTTY) ) { return null }