()
| 72 | * Determines the best available shell to use. |
| 73 | */ |
| 74 | export async function findSuitableShell(): Promise<string> { |
| 75 | // Check for explicit shell override first |
| 76 | const shellOverride = process.env.CLAUDE_CODE_SHELL |
| 77 | if (shellOverride) { |
| 78 | // Validate it's a supported shell type |
| 79 | const isSupported = |
| 80 | shellOverride.includes('bash') || shellOverride.includes('zsh') |
| 81 | if (isSupported && isExecutable(shellOverride)) { |
| 82 | logForDebugging(`Using shell override: ${shellOverride}`) |
| 83 | return shellOverride |
| 84 | } else { |
| 85 | // Note, if we ever want to add support for new shells here we'll need to update or Bash tool parsing to account for this |
| 86 | logForDebugging( |
| 87 | `CLAUDE_CODE_SHELL="${shellOverride}" is not a valid bash/zsh path, falling back to detection`, |
| 88 | ) |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | // Check user's preferred shell from environment |
| 93 | const env_shell = process.env.SHELL |
| 94 | // Only consider SHELL if it's bash or zsh |
| 95 | const isEnvShellSupported = |
| 96 | env_shell && (env_shell.includes('bash') || env_shell.includes('zsh')) |
| 97 | const preferBash = env_shell?.includes('bash') |
| 98 | |
| 99 | // Try to locate shells using which (uses Bun.which when available) |
| 100 | const [zshPath, bashPath] = await Promise.all([which('zsh'), which('bash')]) |
| 101 | |
| 102 | // Populate shell paths from which results and fallback locations |
| 103 | const shellPaths = ['/bin', '/usr/bin', '/usr/local/bin', '/opt/homebrew/bin'] |
| 104 | |
| 105 | // Order shells based on user preference |
| 106 | const shellOrder = preferBash ? ['bash', 'zsh'] : ['zsh', 'bash'] |
| 107 | const supportedShells = shellOrder.flatMap(shell => |
| 108 | shellPaths.map(path => `${path}/${shell}`), |
| 109 | ) |
| 110 | |
| 111 | // Add discovered paths to the beginning of our search list |
| 112 | // Put the user's preferred shell type first |
| 113 | if (preferBash) { |
| 114 | if (bashPath) supportedShells.unshift(bashPath) |
| 115 | if (zshPath) supportedShells.push(zshPath) |
| 116 | } else { |
| 117 | if (zshPath) supportedShells.unshift(zshPath) |
| 118 | if (bashPath) supportedShells.push(bashPath) |
| 119 | } |
| 120 | |
| 121 | // Always prioritize SHELL env variable if it's a supported shell type |
| 122 | if (isEnvShellSupported && isExecutable(env_shell)) { |
| 123 | supportedShells.unshift(env_shell) |
| 124 | } |
| 125 | |
| 126 | const shellPath = supportedShells.find(shell => shell && isExecutable(shell)) |
| 127 | |
| 128 | // If no valid shell found, throw a helpful error |
| 129 | if (!shellPath) { |
| 130 | const errorMsg = |
| 131 | 'No suitable shell found. Claude CLI requires a Posix shell environment. ' + |
no test coverage detected