セットアップのページを読み直していた夜、「Install on network storage」という小さな節で手が止まりました。セッションの途中で Bus error が出て落ちる、という一行です。
私自身の作業環境は自宅の iMac が一台で、共有ホームは使っておりません。ですから、この症状に当たったわけではありません。それでも、会社や研究室のように 1 つのホームを何台もの機械で共有している方には、ある日突然やってくる種類のトラブルだと思います。公式の注意書きを、手元で動く形に直して残します。
先に結論です
最初にお伝えしたいのは、落ちる原因は「実行中のファイルが他の機械から消される」ことで、止めるべきなのは自動更新の「すべての経路」と、古い版を消す手だということです。
DISABLE_AUTOUPDATER を入れれば済むと読んだのが、私の最初の勘違いでした。公式は、それだけでは足りないと明記しています。
症状と、落ちる理由
動いている Claude Code は、起動時だけでなく作業の途中でも、実行ファイルの一部をディスクから読みます。そのファイルが途中で切り詰められたり削除されたりして読めなくなると、セッションが落ちます。Linux のシェルでは Bus error として見えます。
ホームがネットワークストレージ上にあると、自分の機械の更新が、別の機械で動いているセッションの足元のファイルを消してしまうことがあります。機械は他の機械のプロセスを見られないので、「実行中のプロセスがないことを確かめてから消す」という手も使えません。
消されるのは二通りです
公式の説明から、消える筋道を二つに分けました。
| 経路 | 何が起きるか |
|---|---|
| npm のグローバル更新 | `npm install -g` による更新は、前の版のバイナリをその場で削除します。共有ストレージ上だと、他の機械で動いている版が消えます。 |
| ネイティブインストーラの掃除 | `~/.local/share/claude/versions/` の古い版を自分で消します。launcher が指す版、同じ機械で実行中の版、新しい 2 つの版を残し、残りを削除します。 |
ネイティブインストーラの掃除は、同じ機械の中だけを見ています。別の機械で古い版を動かしているセッションは、守られません。
止め方は三段階あります
自動更新を止める設定は、効く範囲が違います。
| 設定 | 止まるもの | 止まらないもの |
|---|---|---|
| `DISABLE_AUTOUPDATER` | バックグラウンドの更新確認 | `claude update`、`claude install` |
| `DISABLE_UPDATES` | 手動を含むすべての更新経路 | なし(自前の配布ツールによる導入は別です) |
共有ストレージで使うなら、公式が勧めるのは DISABLE_UPDATES のほうです。一人でもコマンドを打つ人がいれば、DISABLE_AUTOUPDATER だけでは同じことが起きます。
止めるのは「自動」だけではなく、「誰かが手で打てる更新」まで。 この線引きが、今回の核です。
置き場の線引き
公式が挙げる配置は、次の三つに絞れます。
- バイナリは、機械ごとのローカルディスクに置きます(Linux のパッケージマネージャーや自前の配布ツールなど)。ネイティブインストーラの既定の
~/.local/share/claude/versions/も、npm の既定の prefix も、どちらもホームの中です。 - 版ごとに別のディレクトリに入れ、新しい版は古い版の隣に置きます。その場で上書きしません。
- 古い版を消すのは、どの機械でも動いていないと言い切れるときだけです。
手元で確かめた二つのスクリプト
一つ目は、置き場が共有ストレージ上にないかを見るものです。/proc/mounts から、対象のパスを含むマウントの種別を拾います。
"""Claude Code の実行ファイル置き場が、共有ストレージ上にないかを見る(Linux)。"""
import os
import sys
SHARED = {"nfs", "nfs4", "cifs", "smb3", "smbfs", "fuse.sshfs", "afs", "ceph", "glusterfs"}
def mount_of(path):
path = os.path.realpath(path)
best = ("/", "unknown")
with open("/proc/mounts") as f:
for line in f:
_, mp, fstype, *_ = line.split()
if (path == mp or path.startswith(mp.rstrip("/") + "/")) and len(mp) >= len(best[0]):
best = (mp, fstype)
return best
target = os.path.expanduser(sys.argv[1] if len(sys.argv) > 1 else "~/.local/share/claude")
probe = target
while not os.path.exists(probe) and probe != "/":
probe = os.path.dirname(probe) # まだ無ければ、存在する親で判定する
mp, fstype = mount_of(probe)
verdict = "共有ストレージ: 実行ファイルをローカルへ" if fstype in SHARED else "ローカル扱い"
print(f"対象={target}\n判定に使った場所={probe}\nマウント={mp} 種別={fstype}\n=> {verdict}")作業用の Linux 環境で動かしたところ、種別は ext4 で、判定は「ローカル扱い」でした。まだ存在しないパスを渡しても、存在する親のマウントで判定します。共有ストレージの実機では確かめておりませんので、そこは皆さまの環境で一度お試しいただければと思います。
二つ目は、公式の掃除ルールを写した見積もりです。どの版が残り、どの版が消えるかを出します。版番号は例です。
"""公式の掃除ルール(launcher の指す版・同じ機械で実行中の版・新しい2つを残す)の再現。"""
def key(v): return tuple(int(x) for x in v.split("."))
def survivors(installed, launcher_target, running_here):
newest_two = sorted(installed, key=key)[-2:]
return set(newest_two) | {launcher_target} | set(running_here)
installed = ["2.1.309", "2.1.313", "2.1.314", "2.1.316", "2.1.317"] # 例
keep = survivors(installed, launcher_target="2.1.317", running_here=["2.1.317"])
deleted = [v for v in installed if v not in keep]
print("残る:", sorted(keep, key=key))
print("消える:", deleted)
for host, ver in {"機械B": "2.1.309", "機械C": "2.1.316"}.items():
print(f"{host} が実行中の {ver}:", "バイナリを失う" if ver in deleted else "無事")実行した結果は、次のとおりでした。
残る: ['2.1.316', '2.1.317']
消える: ['2.1.309', '2.1.313', '2.1.314']
機械B が実行中の 2.1.309: バイナリを失う
機械C が実行中の 2.1.316: 無事この表示は公式の文言を写した計算であり、実際の削除を再現したものではありません。それでも、「新しい 2 つを残す」というルールが、古い版を使い続けている機械を守らないことは、数字で見ると一目で分かりました。
一台で使う私は、何を確かめておくか
個人開発で一台の iMac を使う私自身にとっては、共有ホームの話は他人事に見えます。ところが読み返して気づいたのは、止め方の確認はどの環境でも同じ手順だ、ということでした。
設定を入れたあとは、claude doctor を実行し、Auto-updates の行が enabled のままになっていないかを見ます。公式の説明では、DISABLE_AUTOUPDATER が効いていれば disabled (set by env: DISABLE_AUTOUPDATER) と表示されます。設定ファイルを書いただけで安心していた頃の私は、この一行を見ていませんでした。いま思えば、書いたことと効いていることは別だったのです。
結び
まずは、ホームを共有している機械の一つで、置き場の確認スクリプトを実行してみてください。種別が共有ストレージだと出たら、バイナリをローカルへ移し、DISABLE_UPDATES を入れて、更新は自前の配布で行う形に切り替えていただければと思います。
落ちたセッションの作業が戻らない朝の重さを、この一手で先に取り除けるはずです。