mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-07 07:03:00 +08:00
fix: use cli-wrapper.cjs to launch claude-code instead of native binary (#14430)
### What this PR does Before this PR: Cherry Studio launches `claude-code` by having Bun execute `bin/claude.exe`. Since `@anthropic-ai/claude-code` switched to native binary distribution, `bin/claude.exe` is now a Mach-O/ELF/PE native binary (or a shell placeholder if postinstall didn't run). Bun parses it as JavaScript and fails on all platforms: - **macOS/Linux**: `error: Expected ";" but found ...` (Bun treats binary bytes as JS) - **Windows**: `error: could not create process — Bun failed to remap this bin` After this PR: Cherry Studio uses `cli-wrapper.cjs` (the official JS launcher shipped with `@anthropic-ai/claude-code`) to launch claude-code. Bun executes `cli-wrapper.cjs`, which locates and `spawnSync`s the correct platform-specific native binary. This works reliably on all platforms regardless of whether `postinstall` ran. Fixes #14426, fixes #14367, fixes #14422 ### Why we need it and why it was done in this way `@anthropic-ai/claude-code` changed its distribution model: the main package's `bin` field points to a native binary (`bin/claude.exe`), with platform-specific binaries in `optionalDependencies`. The official `cli-wrapper.cjs` is provided as a JS fallback launcher that finds and spawns the correct native binary. The following tradeoffs were made: - We always construct the `bun cli-wrapper.cjs` command path even before install (for first-install flow). This works because `cli-wrapper.cjs` is a static file in the npm package that exists as soon as `bun install` completes, independent of `postinstall`. - A fallback to direct binary execution is provided in case `cli-wrapper.cjs` is not found (e.g., older package versions). The following alternatives were considered: - **Execute the native binary directly** (without Bun): Would work on macOS/Linux after `postinstall`, but fails if `postinstall` didn't run, and doesn't integrate with Cherry Studio's Bun-based execution model. - **Upgrade Bun**: Would not fix the root cause — Bun cannot and should not execute native binaries as JS. ### Breaking changes None. ### Special notes for your reviewer - Three code paths were updated: `getClaudeCodeCommand()` (new helper), `getVersionInfo()`, and `run()` (baseCommand construction). - `isPackageInstalled()` still checks `~/.cherrystudio/bin/claude` existence, which remains correct — Bun creates this symlink during `install -g`. **Local reproduction:** ```bash # Bug (old path): Bun tries to parse native binary as JS ~/.cherrystudio/bin/bun ~/.cherrystudio/install/global/node_modules/@anthropic-ai/claude-code/bin/claude.exe --version # → error: Unexpected � # Fix (new path): Bun executes cli-wrapper.cjs which spawns native binary ~/.cherrystudio/bin/bun ~/.cherrystudio/install/global/node_modules/@anthropic-ai/claude-code/cli-wrapper.cjs --version # → 2.1.114 (Claude Code) ``` ### Checklist - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: [Write code that humans can understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans) and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle) - [x] Refactor: You have [left the code cleaner than you found it (Boy Scout Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html) - [x] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com) was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior. - [x] Self-review: I have reviewed my own code (e.g., via [`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`, or GitHub UI) before requesting review from others ### Release note ```release-note Fix: Claude Code fails to launch on all platforms due to @anthropic-ai/claude-code switching to native binary distribution. Cherry Studio now uses the official cli-wrapper.cjs launcher instead of executing the native binary via Bun. ``` Signed-off-by: raymond <13162938362@163.com> Co-authored-by: SuYao <sy20010504@gmail.com>
This commit is contained in:
@@ -129,6 +129,37 @@ class CodeToolsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command to execute claude-code.
|
||||
*
|
||||
* Since @anthropic-ai/claude-code ships a native binary (bin/claude.exe) instead of
|
||||
* a JavaScript file, it cannot be executed via Bun. The official cli-wrapper.cjs is
|
||||
* a JS launcher that locates and spawns the correct platform-specific binary.
|
||||
* We use Bun to run cli-wrapper.cjs, which works on all platforms.
|
||||
*/
|
||||
private async getClaudeCodeCommand(bunPath: string): Promise<string> {
|
||||
const globalInstallDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'install', 'global')
|
||||
const cliWrapperPath = path.join(
|
||||
globalInstallDir,
|
||||
'node_modules',
|
||||
'@anthropic-ai',
|
||||
'claude-code',
|
||||
'cli-wrapper.cjs'
|
||||
)
|
||||
|
||||
if (fs.existsSync(cliWrapperPath)) {
|
||||
logger.debug(`Using cli-wrapper.cjs for claude-code: ${cliWrapperPath}`)
|
||||
return `"${bunPath}" "${cliWrapperPath}"`
|
||||
}
|
||||
|
||||
// Fallback: try to execute the binary directly (works if postinstall ran correctly)
|
||||
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const executableName = await this.getCliExecutableName(codeTools.claudeCode)
|
||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||
logger.warn(`cli-wrapper.cjs not found at ${cliWrapperPath}, falling back to direct execution: ${executablePath}`)
|
||||
return `"${executablePath}"`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate opencode.json config file for OpenCode CLI
|
||||
* Merge approach:
|
||||
@@ -653,11 +684,21 @@ class CodeToolsService {
|
||||
if (isInstalled) {
|
||||
logger.info(`${cliTool} is installed, getting current version`)
|
||||
try {
|
||||
const executableName = await this.getCliExecutableName(cliTool)
|
||||
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||
let versionCommand: string
|
||||
|
||||
const { stdout } = await execAsync(`"${executablePath}" --version`, {
|
||||
// claude-code ships a native binary that cannot be executed via Bun.
|
||||
// Use cli-wrapper.cjs (via Bun) to run --version reliably on all platforms.
|
||||
if (cliTool === codeTools.claudeCode) {
|
||||
const bunPath = await this.getBunPath()
|
||||
versionCommand = await this.getClaudeCodeCommand(bunPath)
|
||||
} else {
|
||||
const executableName = await this.getCliExecutableName(cliTool)
|
||||
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||
versionCommand = `"${executablePath}"`
|
||||
}
|
||||
|
||||
const { stdout } = await execAsync(`${versionCommand} --version`, {
|
||||
timeout: 10000
|
||||
})
|
||||
// Extract version number from output (format may vary by tool)
|
||||
@@ -919,7 +960,17 @@ class CodeToolsService {
|
||||
}
|
||||
}
|
||||
|
||||
let baseCommand = isWin ? `"${executablePath}"` : `"${bunPath}" "${executablePath}"`
|
||||
let baseCommand: string
|
||||
|
||||
// claude-code ships a native binary that cannot be executed via Bun.
|
||||
// Use cli-wrapper.cjs (via Bun) on all platforms for reliable execution.
|
||||
if (cliTool === codeTools.claudeCode) {
|
||||
baseCommand = await this.getClaudeCodeCommand(bunPath)
|
||||
} else if (isWin) {
|
||||
baseCommand = `"${executablePath}"`
|
||||
} else {
|
||||
baseCommand = `"${bunPath}" "${executablePath}"`
|
||||
}
|
||||
|
||||
// Special handling for kimi-cli: use uvx instead of bun
|
||||
if (cliTool === codeTools.kimiCli) {
|
||||
|
||||
Reference in New Issue
Block a user