iPhoneのiSHでClaude Codeは動くのか試してみた(結果:起動はするが使えない)

発端は、Anthropicが2026年10月12日に出した「AIモデルはデータセンターなしでも自己複製できるかもしれない」というレポートのニュースです。30B以下の小型オープンウェイトモデルを、55ドルのAndroid TVボックス(メモリ4GB)15台に分散させて動かしたとか、隔離環境では GLM-5.2 が8台の仮想機器に自分のコピーを広げられたとか、そういう内容でした。

LLMの推論そのものが数千円のTVボックスで動くなら、推論はサーバー側でやるClaude Codeのクライアントくらい、手元のiPhoneでも動くんじゃないか。ちょうどiPhoneのiSHにSSHで入れるようにしたところだったので、調子に乗って「iSHの中でClaude Codeそのものを動かせないか」を試しました。結果から書くと、Node 18を動かすところまでは成功し、Claude Codeも--helpや認証前の-pまでは動いたのですが、ログインして実際にAPIを呼ぶ段階でiSHごと落ちて終わりました。半日かけて無理だと分かった記録として残しておきます。

環境は以下です。

  • iPhone、iSH 1.3.2(App Store版、中身はAlpine Linux 3.14)
  • PCはMac。作業は全部MacからSSH越しにやりました
  • iSHは32bitのx86(i686)エミュレータ

最初の壁:iSHのAlpineが古くてNode 14しか入らない

iSHのパッケージリポジトリはiSH側で固定されていて(/etc/apk/repositoriesを見るとapk.ish.app/v3.14-2023-05-19になっています)、apk search nodejsで出てくるのは14と16だけです。Claude Codeは最近の版だとNode 22以上を要求するので話になりません。

リポジトリをv3.21に書き換えてapk upgradeする手もありますが、普段使っている環境が壊れるのが怖かったので、別の方法を取りました。

chrootで新しいAlpineを隔離して入れる

Alpineは公式サイトでalpine-minirootfs-3.21.8-x86.tar.gzのような3MB程度の最小ルートファイルシステムを配布しています。これをiSHのホームの下に展開してchrootすれば、iSH本体の環境に触らずに新しいAlpineが使えます。

cd /root
curl -sLO https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/x86/alpine-minirootfs-3.21.8-x86.tar.gz
mkdir alpine321
tar -xzf alpine-minirootfs-3.21.8-x86.tar.gz -C alpine321
cp /etc/resolv.conf alpine321/etc/
chroot alpine321 /bin/sh

これが普通に動いたときはちょっと感動しました。chrootの中でapk add nodejs npmも通ります。

ただしminirootfsには/devの中身がほぼ無いので、/dev/nullや/dev/urandomはmknodで作る必要があります。あとiSHのchrootには癖があって、中でpwdを打つと/root/alpine321/rootのように外側から見たパスが返ってきます。この癖のせいで後で一手間増えました。

Node 22と20は落ちる、Node 18は動く

Alpine 3.21でNode 22を入れてnode -e 1を打ったら即死でした。

Bad system call

iSHが実装していないシステムコールをNode 22が起動時に呼んでいるようです。UV_USE_IO_URING=0や--jitlessを付けても変わりませんでした。

Alpine 3.20のNode 20はnode -e 1は通るのですが、npm --versionを打つと今度はIllegal instructionで落ちます。V8のJITがiSHにない命令を吐いているんだと思います。

Alpine 3.18のNode 18は普通に動きました。node -eの起動が0.9秒、npmも使えます。というわけで3つのバージョンを試して、Node 18だけが生き残りました。

AlpineNode結果
3.2122.23Bad system callで起動すらしない
3.2020.15nodeは動くがnpmがIllegal instruction
3.1818.20動く

最新のClaude Codeはそもそも入らない

ここで調べて分かったのですが、Claude Codeのnpmパッケージは2.1.113以降、中身がただのインストーラーになっています。npm installするとOSとCPUに合ったネイティブバイナリをoptionalDependenciesから持ってくる仕組みで、対応しているのはmacOS、Linux x64/arm64、Windowsだけ。32bitのx86用は存在しないので、どうあがいても最新版は入りません。

JSのCLIを同梱していた最後の版は2.1.112で、これはNode 18以上で動く仕様でした。なのでこれを狙います。

chroot alpine3.18 /bin/sh
npm install -g --ignore-scripts @anthropic-ai/claude-code@2.1.112

--ignore-scriptsを付けているのは、さっきのchrootのパス漏れのせいでpostinstallが自分のファイルを見失って落ちるからです。

chrootを使わずにNodeを起動する

さらにchrootのパス漏れはClaude Code本体にも効いてきて、作業ディレクトリの認識がおかしくなります。回避策として、chrootせずにmuslのローダーを直接叩いてchroot内のNodeを動かすことにしました。

/root/alpine3.18/lib/ld-musl-i386.so.1 \
  --library-path /root/alpine3.18/usr/lib:/root/alpine3.18/lib \
  /root/alpine3.18/usr/bin/node --version

これだとpwdも正しく、ライブラリだけchroot側のものを使えます。ただしICUのデータファイルを探しに行く先がずれるのでICU_DATA=/root/alpine3.18/usr/share/icu/73.2を指定する必要がありました(指定しないとlocaleCompareがIcu errorで死にます)。

claude --versionは18秒で動いた

ここまででclaude --versionが通りました。18秒かかりますが、ちゃんと2.1.112 (Claude Code)と出ます。

しかし--helpや-pを打つと、V8がこう言って落ちました。

# Fatal error in , line 0
# unreachable code

切り分けたところ、空のWebAssemblyモジュールを作るだけで同じ落ち方をします。iSHの上ではV8のWebAssemblyエンジンが動きません。

WebAssemblyを避けるために3つ差し替える

対策は--jitlessでJITごとWebAssemblyを切ることなのですが、そうするとNode 18内蔵のfetch(undici)がWebAssemblyに依存しているのでWebAssembly is not definedで落ちます。なので内蔵fetchを--no-experimental-fetchで外して、純JSのnode-fetchとformdata-nodeを--requireで先に読み込ませてグローバルに生やしました。

// /root/fetch-polyfill.cjs
const nf = require("/root/alpine3.18/usr/local/lib/node_modules/node-fetch");
globalThis.fetch = nf; globalThis.Headers = nf.Headers;
globalThis.Request = nf.Request; globalThis.Response = nf.Response;
const fd = require("/root/alpine3.18/usr/local/lib/node_modules/formdata-node");
globalThis.FormData = fd.FormData; globalThis.File = fd.File;

最終的なラッパーはこうなりました。

#!/bin/sh
R=/root/alpine3.18
export USE_BUILTIN_RIPGREP=0 DISABLE_AUTOUPDATER=1
export ICU_DATA="$R/usr/share/icu/73.2"
export NODE_OPTIONS="--jitless --no-experimental-fetch --max-old-space-size=768 --require /root/fetch-polyfill.cjs"
exec "$R/lib/ld-musl-i386.so.1" --library-path "$R/usr/lib:$R/lib" "$R/usr/bin/node" \
  "$R/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js" "$@"

USE_BUILTIN_RIPGREP=0は、同梱のripgrepがx64用で動かないのでapkで入れたrgを使わせるためです。DISABLE_AUTOUPDATER=1は、うっかりアップデートされると最新版(ネイティブバイナリ)に置き換わって二度と動かなくなるからです。

これでclaude --helpが通り、認証なしのclaude --bare -p "pong"も40秒で「Not logged in · Please run /login」まで正常に走りました。認証以外は全部動いている状態です。

認証を通したらiSHごと落ちた

ここからが駄目でした。

まず対話モードでclaudeを起動して/loginしようとしたら、初回設定のテーマ選択画面が描画された直後にiSHアプリごと落ちます。3回やって3回落ちました。エラーが画面に出るのではなくアプリが消えるので、iOSにメモリで止められているか、iSH内部でクラッシュしているかのどちらかです。

対話モードが駄目なら、と、Macでログイン済みの資格情報を~/.claude/.credentials.jsonとしてiPhoneにコピーして-pで実行してみました。これもAPIを呼ぶ直前で落ちます。資格情報を置く前は同じコマンドが正常に終わっていたので、認証を通過した先の処理が引き金なのは確実です。子プロセスの起動を省く--bareにCLAUDE_CODE_OAUTH_TOKENでトークンを渡す形も試しましたが同じでした。

試したこと結果
claude --version動く(18秒)
claude --help動く
-p 認証なし動く。「Not logged in」まで正常終了
-p 資格情報ファイルありiSHごと落ちる
--bare -p + CLAUDE_CODE_OAUTH_TOKENiSHごと落ちる
対話モード初回設定画面の直後にiSHごと落ちる

落ちるときにログが残らないのでこれ以上は絞れず、ここで諦めました。

分かったこと

  • iSHで新しいAlpineを使いたいときは、minirootfsを展開してchrootするのが安全で手軽。ただし/devは自分で作る、pwdが外のパスになる、の2点に注意
  • iSHで動くNodeは18まで。20と22はV8がiSHにない命令やシステムコールを使って落ちる
  • iSHではV8のWebAssemblyが動かない。--jitlessで回避できるが、Node内蔵のfetchが道連れになる
  • Claude Codeの最新版はネイティブバイナリ配布なので32bit環境には入らない。JS版の最終は2.1.112
  • 2.1.112は上記の細工で起動まではするが、認証後の処理でiSHが落ちる

Node 18が動く環境としてはこのchrootは便利なので残してあります。Claude CodeをiPhoneから使いたいなら、素直にMac側で動かしてiPhoneからSSHやRemote Controlで操作するのが正解です。iSHにSSHで入れるようにしておけば、Mac側のClaude CodeからiPhoneのファイルを触ることはできるので、方向を逆にすれば用は足ります。

まとめ

  • iSHのAlpineは古いので、minirootfs + chrootで新しいAlpineを隔離して使う
  • Node 18は動く。20と22は落ちる
  • Claude Code 2.1.112なら--jitless、内蔵fetchの差し替え、ICU_DATAの指定で--helpまで動く
  • 認証を通した先でiSHごと落ちるので実用にはならない
  • iPhoneからClaude Codeを使うならMacで動かして遠隔操作するのが正解