ターミナルを開くたびに、5秒待っていた。
正確には5.5秒。time zsh -i -c exitで計ったら5.568秒。毎回ターミナルを開くたびに、プロンプトが出るまでボーッと画面を見つめている。Claude Codeでシェルが走るたびに、裏で5秒食われている。
「まあこんなもんだろ」と思っていた。2年くらい思っていた。
結論から言うと、0.04秒になった。137倍速。体感としては「一瞬」。ターミナルを開いた瞬間にプロンプトが出る。
やったのは.zshrcを3箇所直しただけだ。
犯人を特定する
まず計測。推測するな、計測せよ。
time zsh -i -c exit
これでzshの起動〜終了までの時間が出る。5秒以上なら明らかにおかしい。0.5秒以下なら正常。
犯人を絞り込むには、.zshrcを1行ずつコメントアウトして再計測する。地道だが確実。
自分の環境では、犯人は3つだった:
| 犯人 | 所要時間 | やっていること |
|---|---|---|
| nvm | 約2.5秒 | Node.jsのバージョン管理。起動時に全バージョンをスキャン |
| conda | 約2.0秒 | Pythonの仮想環境。conda initが.zshrcに挿入するフック |
| pyenv | 約0.8秒 | Pythonのバージョン管理。eval "$(pyenv init -)"が重い |
3つ合わせて5.3秒。残りの0.2秒はPATH設定やプラグイン読み込み。つまり、「起動時に全部読み込む」設計が諸悪の根源だった。
解決策:Lazy Load(遅延読み込み)
nvm、conda、pyenvはどれも「初期化スクリプト」を.zshrcで実行する設計になっている。インストール時に自動で.zshrcに追記される。親切だが、起動のたびに数秒かかる。
解決策はLazy Load。「初回使用時に初めて読み込む」パターンに変える。
nvm(2.5秒 → 0秒)
Before(nvm公式が.zshrcに追記するデフォルト):
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
After(Lazy Load版):
export NVM_DIR="$HOME/.nvm"
nvm() {
unfunction nvm
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
nvm "$@"
}
node() { unfunction node npm npx 2>/dev/null; nvm use default >/dev/null 2>&1; command node "$@"; }
npm() { unfunction node npm npx 2>/dev/null; nvm use default >/dev/null 2>&1; command npm "$@"; }
npx() { unfunction node npm npx 2>/dev/null; nvm use default >/dev/null 2>&1; command npx "$@"; }
仕組みは単純だ。nvm、node、npm、npxをシェル関数として定義する。初回呼び出し時に:
- 関数を削除(
unfunction) - 本物のnvmを読み込む
- 元のコマンドを実行
2回目以降は本物のnvmが直接呼ばれる。ユーザーから見た違いは「初回だけ一瞬遅い」だけ。
conda(2.0秒 → 0秒)
Before(conda init zshが追記するデフォルト):
# >>> conda initialize >>>
__conda_setup="$('/Users/hikaru/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
# ...
fi
unset __conda_setup
# <<< conda initialize <<<
After(Lazy Load版):
conda() {
unfunction conda
__conda_setup="$('/Users/hikaru/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "/Users/hikaru/anaconda3/etc/profile.d/conda.sh" ]; then
. "/Users/hikaru/anaconda3/etc/profile.d/conda.sh"
else
export PATH="/Users/hikaru/anaconda3/bin:$PATH"
fi
fi
unset __conda_setup
conda "$@"
}
nvmと同じパターン。conda activateを打つまで読み込まない。
pyenv(0.8秒 → 0秒)
Before:
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
After:自分の場合、pyenvは使っていなかった。消した。
使っている人は同じLazy Loadパターンで:
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
pyenv() {
unfunction pyenv
eval "$(command pyenv init -)"
pyenv "$@"
}
計測結果
Before: 5.568s
After: 0.040s
137倍速。
ターミナルを開いた瞬間にプロンプトが出る。Claude Codeのシェル実行も体感で速くなった。当たり前だ。Bashツールが呼ばれるたびに裏で5秒待っていたのが消えたんだから。
なぜ気づかなかったのか
2年間、5.5秒の起動を「こんなもんだろ」で受け入れていた。理由は3つある。
-
インストーラが勝手に
.zshrcに書く。nvmもcondaもpyenvも、セットアップ時に.zshrcを自動編集する。「公式が入れたものだから正しいだろう」と思い込む - ターミナルを開く頻度が低かった。ブラウザ中心の作業なら、5秒の遅延は気にならない。でもClaude CodeやCursorでターミナルが主戦場になると、毎回の5秒が地獄になる
- 「遅い」と認識していなかった。比較対象がないから遅いかどうかわからない。0.04秒を体験して初めて「5.5秒は異常だった」と気づく
Claude Code / Cursor ユーザーが確認すべきこと
Claude Code、Cursor、Antigravityなど、ターミナルをガシガシ使うツールを導入すると、シェルの起動速度がダイレクトに体験品質に影響する。
特に確認してほしいのは:
1. zshの起動時間
time zsh -i -c exit
0.5秒以上なら、.zshrcを見直す価値がある。
2. MCPのゾンビプロセス
Claude CodeでMCPサーバーを複数接続していると、セッション終了後にプロセスが残ることがある。使っていないMCPが設定に残っていると、セッション起動のたびにプロセスが増殖する。
今日、NotebookLM MCPが120プロセスに増殖してCPU 2800%、スワップ8.5GBを食っていた。詳しくは別記事に書いた:「NotebookLM MCPを繋いだら、プロセスが120個に増殖してMacが死んだ」
# MCPプロセスの確認
ps aux | grep -i mcp | grep -v grep
# 大量に出てきたら要注意
ps aux | grep -i mcp | grep -v grep | wc -l
対策:
- 使っていないMCPは
claude mcp remove [名前]で設定から外す。killだけでは再スポーンする -
Activity Monitorで定期的にCPU使用率を確認する - ファンが回り始めたら疑う
3. Homebrew / npmのキャッシュ
長期間使っているとキャッシュが肥大化する。特にnpxのキャッシュは数GBになることがある。
# npxキャッシュの場所
ls -lh ~/.npm/_npx/
# Homebrewのクリーンアップ
brew cleanup --prune=0
まとめ
.zshrcを3箇所直しただけで137倍速になった。
「遅い」と思っていなくても計測してみてほしい。自分の5.5秒は「遅い」じゃなくて「壊れている」だった。137倍速を体験したら、もう戻れない。
time zsh -i -c exit、試してみたら何秒でしたか? コメントで教えてもらえると嬉しい。