Skip to content

Fix Claude binary path corruption from shell integration escape sequences#20

Open
brndnsvr wants to merge 1 commit intolcoutodemos:mainfrom
brndnsvr:fix/shell-integration-binary-path
Open

Fix Claude binary path corruption from shell integration escape sequences#20
brndnsvr wants to merge 1 commit intolcoutodemos:mainfrom
brndnsvr:fix/shell-integration-binary-path

Conversation

@brndnsvr
Copy link
Copy Markdown

Problem

Clui CC silently fails to spawn Claude CLI subprocesses when the user has terminal shell integration enabled (iTerm2, VS Code terminal, Ghostty, etc.) and/or has Claude CLI installed at ~/.local/bin/claude.

The app launches, the UI renders, but prompts never produce responses. There are no user-visible error messages — the debug log reveals the issue:

Claude binary: ]1337;RemoteHost=user@host]1337;CurrentDir=/path]1337;ShellIntegrationVersion=14;shell=zsh/Users/user/.local/bin/claude

The resolved binary path is contaminated with OSC escape sequences, so spawn() fails silently.

Root Cause

The binary discovery fallback in run-manager.ts, pty-run-manager.ts, and process-manager.ts uses an interactive shell (-i flag):

execSync('/bin/zsh -ilc "whence -p claude"', ...)

Interactive shells source .zshrc, which loads shell integrations that emit escape sequences to stdout. These get concatenated with the actual binary path, producing a garbage string.

The same issue affects cli-env.ts PATH construction (/bin/zsh -ilc "echo $PATH") and whisper binary lookup in index.ts.

Who This Affects

This hits anyone with either or both of:

  1. A terminal emulator with shell integration — these inject OSC escape sequences into shell stdout, contaminating any path resolved via execSync with a login shell:

    • iTerm2 (shell integration enabled by default)
    • VS Code integrated terminal
    • Ghostty
    • Warp, Kitty, WezTerm
  2. Claude CLI installed at ~/.local/bin/claude — common with newer Node/npm setups. This path wasn't in the hardcoded candidates list, forcing the shell fallback path where contamination occurs.

Terminal.app and Alacritty are not affected — they don't inject shell integration sequences.

Changes

cli-env.ts:

  • Add ~/.local/bin to the hardcoded PATH entries
  • Drop -i (interactive) flag from shell fallback commands
  • Add stripShellEscapes() helper to clean residual escape sequences from shell output
  • Add extractAbsoluteShellPath() — shared helper that strips escapes and validates the result is an absolute path

run-manager.ts, pty-run-manager.ts, process-manager.ts:

  • Add ~/.local/bin/claude to the candidates list (checked first via direct test -x)
  • Drop -i flag, add timeout: 3000 to shell fallbacks
  • Use shared extractAbsoluteShellPath() instead of inline .trim()

index.ts:

  • Fix whisper binary lookup (same escape contamination issue)
  • Add env: getCliEnv() and timeout: 3000 to whisper shell commands

Test plan

  • Verified on macOS 26.4 with iTerm2 shell integration and Claude CLI at ~/.local/bin/claude
  • npm run build passes cleanly
  • Debug log shows clean binary path after fix
  • App successfully spawns Claude subprocesses and responds to prompts

The binary discovery fallback uses interactive shell (-i flag) to
locate claude/whisper binaries via whence/which. Interactive shells
source .zshrc which loads shell integrations (iTerm2, VS Code, Warp)
that emit OSC escape sequences to stdout, contaminating paths.
The same issue affects cli-env.ts PATH construction.

This silently breaks subprocess spawning for anyone with terminal
shell integration enabled — the app renders but Claude processes
never start.

Changes:
- Add ~/.local/bin/claude to candidates list
- Drop -i flag from shell fallbacks, add timeout: 3000
- Extract shared stripShellEscapes() and extractAbsoluteShellPath()
  helpers in cli-env.ts; use across all binary finders
- Fix whisper binary lookup in index.ts (same escape contamination)
@brndnsvr brndnsvr force-pushed the fix/shell-integration-binary-path branch from 302f784 to 8f1e8c6 Compare March 19, 2026 02:39
@brndnsvr brndnsvr marked this pull request as ready for review March 19, 2026 02:39
Copilot AI review requested due to automatic review settings March 19, 2026 02:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a macOS-specific subprocess discovery failure where execSync output from login shells can be polluted by terminal shell-integration OSC/ANSI escape sequences, causing invalid binary paths (notably for Claude CLI and whisper) and silent spawn failures.

Changes:

  • Adds ~/.local/bin (and ~/.local/bin/claude) to binary/path discovery to cover common modern installs.
  • Switches shell fallbacks from interactive login shells to non-interactive login shells and adds timeouts to avoid hangs.
  • Introduces shared sanitization/parsing helpers (stripShellEscapes, extractAbsoluteShellPath) to clean shell output before using it as a path.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/main/cli-env.ts Builds a more robust CLI PATH and adds helpers to strip escape sequences and safely extract absolute paths from shell output.
src/main/process-manager.ts Updates Claude binary discovery to prefer ~/.local/bin/claude, removes interactive shell usage, and sanitizes shell-resolved paths.
src/main/index.ts Fixes whisper binary lookup by using getCliEnv(), adding timeouts, and sanitizing shell output before treating it as a path.
src/main/claude/run-manager.ts Aligns Claude binary discovery with non-interactive login shells, timeouts, and sanitized path extraction.
src/main/claude/pty-run-manager.ts Same Claude binary discovery hardening for the PTY transport path.
Comments suppressed due to low confidence (1)

src/main/process-manager.ts:45

  • The NVM candidate join(homedir(), '.nvm/versions/node', '**', 'bin/claude') is effectively dead: it’s used with test -x "${c}", but ** won’t expand because it’s quoted (and /bin/sh doesn’t support globstar by default). Either replace this with an explicit directory scan (iterate ~/.nvm/versions/node/*/bin/claude via fs) or drop the candidate and rely on the shell fallback.
      join(homedir(), '.npm-global/bin/claude'),
      join(homedir(), '.nvm/versions/node', '**', 'bin/claude'),

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mite404
Copy link
Copy Markdown

mite404 commented Mar 19, 2026

I was wondering why my recent commands were printing garbage. thx!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants