何が起きているのか
リポジトリをクローンまたはプッシュしていて、数秒間は問題なく動いているのに、途中でGitが落ちてしまう:
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: the remote end hung up unexpectedly
原因はほぼ間違いなくリポジトリのサイズだ。GitのデフォルトHTTPバッファはたった1MBしかない。リポジトリが大きい場合——たとえば何年もの履歴とバイナリアセットを含む500MBのリポジトリ——サーバーがデータを送り始め、バッファが満杯になり、接続が止まり、リモートが切断してしまう。
パーミッションの問題ではない。ファイアウォールでもない。GitのHTTPレイヤーとリモートサーバー間のバッファ/タイムアウトのミスマッチだ。
デバッグ:根本原因を確認する
設定を変更する前に、実際に何が起きているかを確認しよう。
# 現在のhttp.postBuffer設定を確認(デフォルトは1MB = 1048576)
git config --global http.postBuffer
# 出力がない場合は、まだ1MBのデフォルトのまま
次に、完全な詳細ログを有効にしてクローンを実行し、どこで失敗するかを正確に特定する:
GIT_CURL_VERBOSE=1 GIT_TRACE=1 git clone https://github.com/your/repo.git 2>&1 | tail -50
RPC failed、curl: (18)、またはtransfer closedを探そう。これら3つの文字列が確認できれば、バッファの問題であることが確定し、以下の修正で解決できる。
解決策1:HTTPポストバッファを増やす(ほとんどのケースに有効)
まずここから始めよう。この1つの設定で、curl 18エラーの約90%が解決される。
# 500MB — ほとんどの大きなリポジトリに対応
git config --global http.postBuffer 524288000
# 1GB — 1GB超のリポジトリや非常に長い履歴の場合
git config --global http.postBuffer 1073741824
クローンまたはプッシュを再試行する:
git clone https://github.com/your/repo.git
# または
git push origin main
**確認方法:**RPCエラーが出なければ成功だ。クローンしたディレクトリの中でgit log --oneline -5を実行して、履歴が正しく取得できているか確認しよう。
解決策2:まずシャロークローンして、その後フル履歴を取得する
コミットが10,000件以上あるリポジトリもある。そういった場合は500MBのバッファでも十分でないことがある。最新のスナップショットだけをクローンして、残りを小さな単位で取得しよう。
# 最新のコミットだけをダウンロード — 速くて軽い
git clone --depth 1 https://github.com/your/repo.git
cd repo
# 完全な履歴を段階的なチャンクで取得する
git fetch --unshallow
--depth 1のクローンでは、全履歴の代わりに数MBしか転送しない。安定した接続でディレクトリに入ってから、--unshallowでバッファを圧迫する大量のバーストではなく、段階的に残りを取得する。
確認方法:--unshallowが完了したら:
git log --oneline | wc -l
# フルコミット数が表示される — 1ではない
解決策3:HTTPSの代わりにSSHに切り替える
curl 18エラーはHTTP経由でのみ発生する。SSHはまったく異なるトランスポートを使用するため、HTTPバッファもRPCレイヤーもcurlも関係ない。
# リモートURLをHTTPSからSSHに切り替える
git remote set-url origin git@github.com:your-username/repo.git
# 変更を確認する
git remote -v
# 再試行
git push origin main
# またはフレッシュにクローン:
git clone git@github.com:your-username/repo.git
SSHはギガバイト単位の転送でも問題なく処理できる。SSHアクセスがある場合、最も信頼性の高い長期的な解決策だ。ポート22をブロックする企業ネットワークでは、SSHをポート443でトンネリングできる場合もある——ssh.github.comの設定についてはGitホストのドキュメントを確認しよう。
解決策4:タイムアウトを無効にして圧縮を下げる
遅い接続(ホテルのWiFi、モバイルホットスポット、VPNのボトルネックなど)では、バッファが満杯になるのではなく、タイムアウト後にサーバーが接続を切断するという別のバージョンのエラーが発生する。2つの設定変更が効果的だ:
# Gitの低速タイムアウトを実質的に無効化する
git config --global http.lowSpeedLimit 0
git config --global http.lowSpeedTime 999999
# zlib圧縮をスキップする — 転送量は増えるがCPUオーバーヘッドが減る
git config --global core.compression 0
lowSpeedLimit 0は転送速度に基づいた中断をGitに行わせないようにする。core.compression 0はファイルサイズをスピードと引き換えにする——各オブジェクトの転送量はやや増えるが、事前の圧縮でCPU時間を消費しない。CPUがボトルネックではない遅い接続では、ほぼ常にメリットが上回る。
低速ネットワークで最良の結果を得るために、これらの設定を解決策1のバッファ増加と組み合わせよう。
解決策5:コミットを小さなバッチに分けてプッシュする
クローンは問題ないのにgit pushが失敗し続ける?おそらく大量のコミットが溜まっているのだろう——1週間以上オフラインで作業した後によくあるケースだ。一度に全部プッシュするのではなく、分割してプッシュしよう。
# プッシュ待ちのコミットを確認する
git log origin/main..HEAD --oneline
# 最初の約100コミットをプッシュする
git push origin <hash-of-100th-commit>:main
# 残りを通常通りプッシュする
git push origin main
実際のハッシュはgit logの出力から取得しよう。未プッシュのコミットが400件ある場合は、タイムアウトする1つの巨大な操作ではなく、100件ずつ4回に分けてプッシュしよう。
修正後の設定を確認する
変更した内容をまとめて確認する:
git config --global --list | grep -E 'http|core.compression'
すべての修正を適用した場合、次のように表示されるはずだ:
http.postbuffer=524288000
http.lowspeedlimit=0
http.lowspeedtime=999999
core.compression=0
どの修正から試すべきか
- まず試すべき:
http.postBufferを増やす(解決策1)——10件中9件はこれで解決する。 - まだ失敗する場合:
--depth 1を使ってから--unshallowする(解決策2)。 - SSHアクセスがある場合: SSHに切り替える(解決策3)——最も恒久的で信頼性が高い。
- 遅いまたは不安定な接続の場合: 解決策4のタイムアウト設定とバッファ増加を組み合わせる。
- プッシュのみの問題の場合: バッチに分けてプッシュする(解決策5)。
得られた教訓
Gitの1MBデフォルトバッファは、ほとんどのリポジトリが小さかった時代の遺産だ。今日のモノレポは2〜5GBに達することもある。10年の歴史、100人のコントリビューター、コミット済みバイナリを持つプロジェクトは、1回のクローン操作で300〜500MBを簡単に転送する——デフォルトバッファで処理できる量の300倍だ。
新しい開発環境をセットアップする際は、大きなものをクローンし始める前にグローバル設定にバッファ設定を追加しておこう。5秒で済み、このような頭痛の種を完全に避けられる。
非常に大きなリポジトリを運用しているチームは、バイナリをメインオブジェクトストアから移動するためにGit LFSも検討すべきだ。また、ルーズオブジェクトをパックするために定期的なgit gc --aggressiveの実行もスケジュールしよう。LFSを使って適切にメンテナンスされたリポジトリは、クローンサイズを80%以上削減できる——転送データが少なければ少ないほど、タイムアウトの問題も大幅に減る。

