• #claude-code
  • #volta
  • #remote-control
  • #windows
  • #node
  • #bugfix
開発claude-code-toolsメモ

Claude Code remote-controlがVolta環境で動かないバグを直した

claude remote-control を実行した瞬間、こう返ってくる。

[08:27:41] Session failed: C:\Users\numbe\AppData\Local\Volta\tools\image\node\22.22.0\node.exe: bad option: --sdk-url session_0196WpHd1WRMRYLxSG7wTBNc

remote-controlはClaude Code 2.1.xで追加された機能で、claude.aiのウェブUIからローカルのClaude Codeセッションを操作できる。しかしVolta環境だとセッション確立直後に死ぬ。


環境

  • Windows 11
  • Node.js v22.22.0(Volta管理)
  • Claude Code 2.1.52(volta install @anthropic-ai/claude-code でインストール)

原因

Claude Codeの remote-control は、リモートセッションが来ると子プロセスを child_process.spawn で起動する。そのコードがこうなっている:

// cli.js 内の IPq 関数(minified)
let O = iHz(A.execPath, $, { cwd: K, stdio: [...], env: H, windowsHide: true });

ここで:

  • iHz = child_process.spawn
  • A.execPath = process.execPath = node.exeのパス
  • $ = ["--print", "--sdk-url", <url>, "--session-id", <id>, ...]

つまり実行されるコマンドは:

node.exe --print --sdk-url wss://... --session-id session_xxx ...

node.exe に直接 --print--sdk-url を渡しているので、Node.jsがこれを自身のオプションと解釈して bad option で落ちる。

正しくは node.exe cli.js --print --sdk-url ... のように、引数の先頭に cli.js のパスが必要。

なぜ公式ビルドで動くのか(推測)

公式のスタンドアロンインストーラーでは、process.execPath がNode.jsバイナリではなくClaude Code自体のバイナリを指す可能性がある(SEA: Single Executable Applicationなど)。npm/Volta経由のインストールでは process.execPath は常に node.exe なので、cli.js パスを別途渡す必要がある。


修正手順

1. 正しいcli.jsの場所を特定する

Volta環境では cli.js が複数箇所に存在する。実際にロードされるのは packages ディレクトリ のほう。

# これは使われない(npm install -g 用)
Volta/tools/image/node/22.22.0/node_modules/@anthropic-ai/claude-code/cli.js

# これが本物(volta install 用)
Volta/tools/image/packages/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code/cli.js

フルパス:

C:\Users\numbe\AppData\Local\Volta\tools\image\packages\@anthropic-ai\claude-code\node_modules\@anthropic-ai\claude-code\cli.js

正しいファイルかどうかは、ファイル先頭に console.error("LOADED") を入れて claude --version を実行すれば確認できる。

2. spawn呼び出しを修正する

cli.js の中で以下の文字列を検索する:

iHz(A.execPath,$,{cwd:K,stdio:["pipe","pipe","pipe"],env:H,windowsHide:!0})

これを以下に置換:

iHz(A.execPath,[process.argv[1],...$],{cwd:K,stdio:["pipe","pipe","pipe"],env:H,windowsHide:!0})

process.argv[1]cli.js のフルパスを指すので、子プロセスが node.exe cli.js --print --sdk-url ... として正しく起動される。

3. 動作確認

claude remote-control

以下のように表示されれば成功:

·✔︎· Connected · project-name · master

Continue coding in the Claude app or https://claude.ai/code/session_xxx
space to show QR code

ワンライナーパッチ(コピペ用)

Pythonで置換する場合:

import re

path = r"C:\Users\numbe\AppData\Local\Volta\tools\image\packages\@anthropic-ai\claude-code\node_modules\@anthropic-ai\claude-code\cli.js"

with open(path, "r", encoding="utf-8") as f:
    content = f.read()

old = 'iHz(A.execPath,$,{cwd:K,stdio:["pipe","pipe","pipe"],env:H,windowsHide:!0})'
new = 'iHz(A.execPath,[process.argv[1],...$],{cwd:K,stdio:["pipe","pipe","pipe"],env:H,windowsHide:!0})'

if old in content:
    content = content.replace(old, new, 1)
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)
    print("Patched successfully")
else:
    print("Target string not found - already patched or different version")

アップデート後の再パッチ

volta install @anthropic-ai/claude-code でアップデートするとパッチが消える。その場合は同じ手順で再度パッチを当てる。

将来のバージョンでは修正されている可能性もあるので、まずパッチなしで claude remote-control を試してから判断すればよい。


調査で嵌ったポイント

最初の1時間は 間違ったcli.jsを編集していた

Voltaは volta install で入れたパッケージと npm install -g で入れたパッケージを別の場所に格納する:

インストール方法格納先
volta installVolta/tools/image/packages/<pkg>/node_modules/
npm install -gVolta/tools/image/node/<version>/node_modules/

which claudenpm list -g が返すパスは後者を指すが、PowerShellで claude を叩いたときにVoltaシムが解決するのは前者。デバッグ出力を入れても何も出ない、パッチが効かない、と悩んだ末にようやく find で全コピーを洗い出して気づいた。


GitHub Issue

この問題はClaude Code本体のバグ。npm/Volta経由でインストールした場合、process.execPathnode.exe を指すのに process.argv[1](スクリプトパス)がspawn引数に含まれていない。

報告先: https://github.com/anthropics/claude-code/issues

再現条件:

  • Windows + Volta(おそらくnvm-windowsやfnmでも同様)
  • volta install @anthropic-ai/claude-code でインストール
  • claude remote-control を実行