Claude CodeのWindows Git Bashでコマンドが約8KBで切れる原因と回避策
WindowsのGit Bashで約8KBを超えるBashコマンドが途中で切れ、バックスラッシュも半分になる不具合の原因と、Writeツール経由・PowerShellツールでの回避策をまとめます。
WindowsでGit BashをBashツールのシェルに使っていると、長いコマンドが unexpected EOF while looking for matching で失敗することがあります。構文は正しいのに落ちるのが特徴です。原因はコマンドの中身ではなく、コマンドがbashへ渡る経路にあります。
GitHubのissue #93618と#93915には、同じ経路に起因する2つの症状が報告されています。約8KBを超えるとコマンドが途中で切れる問題と、連続したバックスラッシュが半分になる問題です。後から立った#102533も同じ症状を測っています。いずれのissueもopenのままなので、当面はコマンドを長くしない運用で避けます。
約8KBを超えると何が起きるか
cat > file <<'EOF' のヒアドキュメントで大きな本文を書かせると、次のようなエラーで終了します。
/usr/bin/bash: -c: line 102: unexpected EOF while looking for matching `''エラーは構文の問題を指していますが、コマンドは壊れていません。#93915の報告者は、失敗した31件のコマンドを取り出して bash -n(構文チェックのみ)にかけ、30件が問題なく通ったと書いています。テキストは正しく、bashに届く前に切られていたわけです。
報告されているサイズ別の結果は次のとおりです。
| コマンドのサイズ | 成功 | 失敗 |
|---|---|---|
| 4KB未満 | 成功1,069 | 失敗0 |
| 4KB〜8KB | 成功77 | 失敗0 |
| 8KB〜32KB | 成功0 | 失敗31 |
| 約32KB超 | 成功0 | 失敗5(uv_spawn の ENAMETOOLONG) |
この集計は#93915の報告者が711セッション分のトランスクリプトから数えた値で、1つの環境の実測です。傾向として、4KBを下回るコマンドは影響を受けず、8KB付近が境界になっています。
なぜ切れるのか:bash -cとMSYS2のargv
Bashツールは、ユーザーのコマンドをそのまま実行するのではなく、シェルスナップショットの読み込みや環境変数の設定を前置した1本の文字列にまとめます。#93618では、おおよそ次の形だと説明されています。
bash -c "source <shell-snapshot> 2>/dev/null || true && export TEMP=... \
&& eval '<command>' < /dev/null && pwd -P >| <cwd-file>"この文字列がWindowsのコマンドライン引数としてGit Bash(MSYS2ランタイム)へ渡ります。#93915が引く上流の報告(msys2-runtime)によると、MSYS2はWin32のコマンドラインから引数を組み立て直す際に glob() を通します。glob() は固定の8192文字バッファで入力を変換するため、それより長い引数は切り詰められます。
glob() の経路に入る条件は、引数に ?*["'(){} のいずれかが含まれることです。シェルのワンライナーは引用符を含むため、多くがこの経路に入ります。
WindowsのCreateProcess自体は約32,767文字まで許すため、Windows側の制限ではありません。#93915の計測では、同じMSYSランタイムの echo.exe は20,000文字、ネイティブの python.exe は32,000文字を受け取れました。詰まるのは bash -c だけです。
単引用符が予算を食う
ユーザーのコマンドは eval '...' の中に入るため、コマンド内の単引用符 ' は '"'"' の5文字に置き換わります。1文字が5文字になるので、実質の予算は見た目より早く尽きます。2つの報告の計測値は次のとおりです。
- #93618:予算は8175文字。単引用符1個につき約4文字分(4.003)を追加で消費し、二重引用符は消費0
- #93915:
bash -cの上限は8,203文字
2つの数字は、前置き部分の長さの見積もりも違います。#93618は前置きを約337文字、#93915は約1KBと書いており、実効予算は環境と版で動きます。#102533の環境でコマンドに使えるのは7774文字なので、7,000文字台前半までを目安にしておくのが安全です。
#102533(open)は、同じ仕組みをより細かく測っています。Windows 11のClaude Code 2.1.295で、bash -c に渡る文字列が8186文字ちょうどで切れる(8186は通り、8187は切れる)と報告されています。前置きと後置きの合計は412文字で、ユーザー名とTempのパスによって変わります。コマンド側に残るのは7774文字です。計測は単引用符1個が5文字('"'"')、通常の文字・二重引用符・$・改行・非ASCIIが1文字で、アポストロフィだけを並べると1,561個で上限に達します。
切れ方は、上限を何文字超えたかで変わります。
| 超過文字数 | 起きること |
|---|---|
| 1〜62 | 起きることコマンドは実行されるが、末尾のcwd記録が切れる。作業ディレクトリの追跡が外れ、Tempに中途半端な名前のファイルができたり、Is a directory と表示されたりする |
| 63〜75 | 起きること< /dev/null が切れ、何も実行されない |
| 76以上 | 起きることeval を閉じる引用符が切れ、何も実行されず unexpected EOF になる |
わずかに超えただけだと、エラーなしにコマンドだけが走るので注意が必要です。報告者は、76文字以上超えた場合に、同じターンの先行する副作用(メッセージ送信など)が済んだあとでBash呼び出しだけ失敗した例もあると書いています。
英文の散文は won't や $(date '+%F') のように単引用符が多く、見た目より早く上限に届きます。1,000個の単引用符を含むコマンドなら、それだけで約4,000文字分が予算から消えます。
Claude Codeを使わずに再現する
#93915には、Claude Codeを介さずPowerShellだけで再現する手順が投稿されています。bash.exe に直接 -c で長い引数を渡し、7,000文字と9,000文字を比べます。
$bash = "C:\Program Files\Git\usr\bin\bash.exe"
foreach ($n in 7000, 9000) {
$cmd = "echo start; : '" + ("a" * ($n - 26)) + "'; echo end"
"{0} chars -> {1}" -f $cmd.Length, ((& $bash -c $cmd 2>&1) -join ' | ')
}投稿された出力では、7,000文字は start | end と最後まで実行され、9,000文字は unexpected EOF while looking for matching になります。同じ文面を bash -n file.sh でファイルから読ませると、構文エラーは出ません。
同じ投稿は、切れる位置が「デコード後の文字数」でなく「Windowsのコマンドライン上の長さ」で決まる、とも報告しています。二重引用符は \" に置き換わって1文字ずつ増えるので、二重引用符を含むコマンドは8,191文字でも失敗し、含まないコマンドは8,191文字まで通って8,192文字目が欠けた、という内容です。C#・JSON・XMLのように二重引用符が多い本文は、8KBより手前で壊れやすくなります。ただし#93618の計測では二重引用符の消費は0で、2つの報告は食い違っています。
バックスラッシュが半分になる別の不具合
もう1つの症状は、サイズと無関係に起きます。#93618と#93915は、どちらもバックスラッシュの連続がbashへ届くまでに半減すると報告しています。
| 送ったもの | bashに届いたもの |
|---|---|
\\ | bashに届いたもの\ |
\\\\ | bashに届いたもの\\ |
C:\Users\name(単独の \) | bashに届いたものそのまま |
#102533は、この半減が単引用符の内側でも起きると報告しています。n個連続したバックスラッシュはn÷2を切り上げた個数で届き(\\\ は \\、\\\\ も \\)、直後が " のときだけ例外です。
エラーは出ません。Pythonの正規表現 r'\\d+' が r'\d+' として書き込まれると、バックスラッシュではなく数字にマッチするコードになり、スクリプトは正常終了したまま誤った動作をします。#93618では、ファイルに書いた \r\n のエスケープが本物の改行になり、原因の特定に4回かかった例が挙がっています。
#93915の計測では、295バイトの小さなコマンドでも破損が出ました。そのため、ペイロードを分割しても、バックスラッシュの問題には効きません。
自分の環境で確かめる
#93618の再現手順は次のとおりです。引用符付きのヒアドキュメントなので、bashはバックスラッシュに触りません。
cat <<'EOF' | od -c
a\b c\\d e\\\\f
EOF期待値は a\b c\\d e\\\\f です。報告された実際の出力は a\b c\d e\\f で、連続部分がすべて半分になっています。macOSのターミナルで同じコマンドを実行すると、出力は期待値のままです。Windowsのターミナルで直接実行した結果と、Claude Code経由の結果に差が出るなら、原因は受け渡しの経路にあると切り分けられます。
unexpected EOFが出たときの見分け方
同じ unexpected EOF while looking for matching でも、原因が別の不具合があります。GitHubでの報告を症状で並べると、見るべき場所が分かれます。
エラーの出方で原因を絞る
長いコマンドだけ失敗する
ヒアドキュメントなど大きな本文を渡したときだけ落ち、同じ文面を bash -n にかけると通ります。#93618と#93915が報告している不具合で、対処はファイル経由です。
小さくても中身が変わる
エラーは出ず、\\ が \ に減ったファイルができます。295バイトでも起きるので、コマンドを短くしても直りません。
短くても毎回失敗する
アポストロフィを含むユーザー名やフォルダーのパスが原因の報告(#28759、#31465、#27212。いずれもclosed)と、シェルスナップショットが約7.2KBで切れる報告(#90421。openで、タイトルはWindowsのDesktop)があります。
#93618自身も、アポストロフィのパスが原因の一連のissueを「別の根本原因」として切り分けています。Bashコマンドが毎回失敗するなら、コマンドの長さでなくプロファイルのパスやスナップショットの側を疑う方が早くなります。
回避策:ファイルに書いてから実行する
両方の症状は、コマンドをコマンドライン引数で渡すことから生じています。#93618は、Writeツールで本文をファイルに書き、そのファイルを実行する方法を確実な回避策として挙げています。WriteツールもPowerShellツールも、MSYS2の引数処理を通りません。
ファイル経由で実行する3ステップ
- 1
Writeツールで本文をファイルに書く
ヒアドキュメントは使いません。
scripts/gen-report.shのようなパスに、スクリプト全体を保存します。 - 2
Bashツールで短く実行する
Bashツールに渡すのは、次の1行だけです。
bash ./scripts/gen-report.sh - 3
一時ファイルを消す
使い捨てのスクリプトなら、実行後に削除します。
コマンドライン引数に載るのは bash ./scripts/gen-report.sh だけなので、サイズ制限にもバックスラッシュの半減にも当たりません。スクリプトの中身は、Writeツールが書いたバイト列がそのまま保存されます。
Writeツールは、新規ファイルなら事前のReadなしで作れます。既存ファイルの上書きは、モデルによっては先にReadが要ります(Claude Opus 4.6やHaiku 4.5以前の世代は常に必要で、新しいモデルは条件付きで不要です)。
PowerShellツールに任せる選択肢
公式ドキュメントによると、WindowsのPowerShellツールはBashツールと別経路でコマンドを実行します。Git Bashを入れていないWindowsでは自動で有効になり、Git Bashがある環境でも、claude.aiとConsoleのアカウントでは既定でオンです。Bedrock・Google Cloud・Foundryのセッションで使うには、CLAUDE_CODE_USE_POWERSHELL_TOOL=1 を設定します。
{
"env": {
"CLAUDE_CODE_USE_POWERSHELL_TOOL": "1"
}
}有効にすると、Claudeは主なシェルとしてPowerShellを扱い、POSIXスクリプトが必要なときだけBashツールを使います。#93618は、PowerShellツールはこの問題の影響を受けないと報告しています。ただしPowerShellの構文で書き直す必要があり、プレビュー段階の制限(プロファイルが読み込まれない、Windowsではサンドボックス未対応)も公式に明記されています。
Git Bashの自動検出がうまくいかない環境の設定はscoopのGit Bashを指定する設定に、Git Bashを使わない構成はv2.1.120のリリースノートにあります。
効かない対処
issueに、試して失敗した方法が書かれています。
MSYS=noglobを設定する:bash -cの上限は8,203から32,731に上がります。しかし引用符のあるコマンドで引数の分割が壊れ、evalを使う経路では必ずunexpected EOFになります。#93618でも同じ理由で不採用です- コマンドを分割して送る:切り詰めは避けられますが、バックスラッシュの半減は295バイトでも起きるので直りません
- Base64にして渡す:それでも引数として渡るため、サイズが約33%増えて悪化します
- ヒアドキュメントの引用を書き換える:原因は構文でないため、効果がありません
長いコマンドをフックで止める
長いコマンドが通ってしまうと、失敗の理由が構文エラーに見えて時間を取られます。PreToolUseフックでBashのコマンド長を測れば、上限に届く前に止めて、Claudeへ「ファイルに書け」と伝えられます。フックは標準入力のJSONから tool_input.command を読み、終了コード2で呼び出しをブロックします。標準エラー出力の文言はブロックの理由として扱われます。
.claude/settings.json の設定例です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}/.claude/hooks/limit-bash-length.sh\""
}
]
}
]
}
}args を付けるとシェルを通さないexec形式になり、Windowsでは command が .exe のような実行ファイルでなければなりません。.sh を呼ぶこの例では args を使わず、シェル形式で bash 経由に起動しています。
このフックは Bash だけを対象にします。#93618はPowerShellツールが影響を受けないと報告しているため、PowerShellの長いコマンドは止めなくて構いません。PowerShellツールの呼び出しにも別のフックを掛けたいときは、matcherを Bash|PowerShell にします。Bash だけではPowerShellツールに掛かりません。
.claude/hooks/limit-bash-length.sh は、単引用符の膨張分を足した長さで判定する例です。しきい値の7000は、issueの実測(8175〜8203)から余裕を見た値で、公式の数字ではありません。#102533の報告者も同じ発想のフックを使っていて、長さに単引用符の4倍を足した値が7700を超えたらブロックしています。
#!/bin/bash
# .claude/hooks/limit-bash-length.sh
COMMAND=$(jq -r '.tool_input.command')
QUOTES=$(printf '%s' "$COMMAND" | tr -cd "'" | wc -c)
SIZE=$(( ${#COMMAND} + QUOTES * 4 ))
if [ "$SIZE" -gt 7000 ]; then
echo "Bashコマンドが長すぎます(実効${SIZE}文字)。" >&2
echo "Writeツールでファイルに書き、bash <file> で実行してください。" >&2
exit 2
fi計算の例として、x を7,000個並べた echo は echo の5文字を足した実効7,005文字でブロックされます。won't を900回並べた約5,400文字のコマンドは、単引用符900個ぶんの3,600文字が加わって実効9,005文字になり、短く見えても止まります。フックの標準入力の形と、終了コード2がPreToolUseの呼び出しをブロックする挙動は公式のhooksドキュメントに沿っています。このスクリプトが動くにはjqが要るため、Windowsでは導入状況を先に確かめてください。
CLAUDE.mdに書く規約
フックで止める前に、そもそも長いコマンドを書かせない方が手戻りは少なくなります。Windowsのプロジェクトでは、CLAUDE.md に次のような規約を置く方法があります。
## Windows Git Bashでのコマンド
- 2KBを超える本文や、バックスラッシュを含むスクリプトは、Bashのヒアドキュメントで
書かず、Writeツールでファイルにしてから `bash <file>` で実行する
- 正規表現やUNCパスなど `\\` を含む内容は、必ずWriteツール経由で保存する
- Bashで `unexpected EOF while looking for matching` が出たら、引用符を
書き換える前にコマンドの長さを疑う2KBという基準は、4KB未満は無傷という報告の実測より保守的に取った値です。バックスラッシュ入りの内容は、サイズに関係なくWriteツールに寄せるのが実態に合います。
ヒアドキュメントなど複数行コマンドの自動承認が効かない条件は、複数行コマンドの自動承認にまとめてあります。
出力の切り詰めとは別の問題
この記事で扱った切り詰めは、コマンドの入力側で起きます。Bashの出力が長すぎて切られる問題は別物で、BASH_MAX_OUTPUT_LENGTH で調整する話はBASH_MAX_OUTPUT_LENGTHの解説にあります。
修正の状況
#93618・#93915・#102533はいずれもopenのままで、提案されている修正方針は次の2つです。
- コマンドを標準入力(
bash -s)で渡す。#93915では259KBまで同一のバイト列で届いたと報告されています - 一時ファイルに書いて
bash <file>で実行する。引数の処理を通らないので、長さの制限とバックスラッシュの半減が同時に消えます
どちらも、コマンドを引数として渡すのをやめる方向です。#93618はさらに、上限を超えたときに構文エラーを装わず、明確なエラーを返すことも提案しています。
changelogには、これら3件のissueに対応する修正項目が載っていません。修正が入るまでは、ファイルに書いてから実行する運用が実質的な対処です。同じGit Bash環境では、バックグラウンドタスクの終了後に検索コマンドが残る問題もあり、見分け方と終了手順はfind.exeが残るときの後始末にまとめています。
まとめ
Windows + Git Bashで長いコマンドが切れる、バックスラッシュが減るという症状は、コマンドを引数で渡す経路の問題なので、書き方を直さずファイル経由に切り替えるのが近道です。短いコマンドでも毎回 unexpected EOF になるときは、パスやシェルスナップショットを疑ってください。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。