Claude Code Windowsで「unexpected EOF」——8KB超のheredocが壊れ、バックスラッシュが消える原因
Windows版Claude CodeでBashコマンドが8KBを超えるとunexpected EOFで失敗し、バックスラッシュも消える不具合を整理。原因はMSYS2 bashのargv経由8192バイト制限。再現データと今すぐできる回避策をまとめた。
エンジニアのゆとです。
Windows版のClaude Codeで、大きめのファイルをheredoc(cat > file <<'EOF' ... EOF)で書き込もうとすると/usr/bin/bash: -c: line 102: unexpected EOF while looking for matching \”`のようなエラーで止まる、という報告をGitHubのIssueで見つけた。しかも厄介なことに、このエラーが出ずに「成功」した場合でも、書き込んだ内容のバックスラッシュがこっそり半分に減っていることがあるらしい。
自分はふだんmacOS+WSLの組み合わせで動かしてるので、Git Bash特有のこの不具合を自分の環境で踏んだことはない。ただ、報告の中身が異常に細かくて——bisectionで閾値を測定し、31件の失敗コマンドをbash -nの構文チェックにかけ直し、バイト単位で入出力を比較する、というところまでやってある。ここまで再現条件が詰められた一次情報はそうないので、Windowsで開発してる人向けに、症状と原因、そして今すぐできる回避策を整理しておく。
まず結論 — 何が起きていて、原因は何か
| 症状 | 発生条件 | 原因 |
|---|---|---|
unexpected EOF while looking for matching でheredoc書き込みが失敗する | コマンドが約8,000バイトを超えたとき | MSYS2 bashのargv再構築処理が固定8,192バイトのスタックバッファで打ち切られる |
バックスラッシュのペア(\\)が半分に減る | サイズに関係なく、常に | 同じargv再構築処理がWindows流のエスケープ規則を適用してしまう(推測、未確定) |
| どちらもClaude Code側にエラー表示が出ない、または見当違いの行を指す | 上記どちらの症状でも | 壊れているのは転送経路であって、コマンド自体の構文は正しいため |
どちらの症状も、Windows版Claude CodeがBashツールのコマンドをGit Bashのbash.exe -cに「引数(argv)」として渡していることが根本原因。同じ内容をbashの標準入力(bash -s)経由で渡すと、259KBまでバイト単位で一致することが確認されている。つまりbash自体にもWindowsにも問題はなく、両者をつなぐMSYS2ランタイムの実装が原因、というのがこのIssueの整理だ。
症状1 — 8KB超のheredocが「見当違いの行」でエラーになる
エラーメッセージは毎回こういう形になる。
/usr/bin/bash: -c: line 102: unexpected EOF while looking for matching `''
紛らわしいのは、指摘される行番号が実際に構文の壊れている場所ではなく、たまたまアポストロフィを含む行(won'tやHDD's、$(date '+%Y-%m-%d')など)を指す点。報告者はこの31件の失敗コマンドを実際のセッション記録から抽出して、構文チェック専用のbash -nに再度通している。結果は31件中30件が「構文エラーなし」——つまりコマンド自体は正しく、単に転送の途中で切り落とされていただけだったことになる。
もう一つの根拠は、エラーが指す行番号がコマンドサイズに関わらず一定だったこと。8KBから24KBまでサイズを変えても、常に同じ行番号でエラーが出た。内容によって位置が変わる構文エラーならこうはならない。固定位置での打ち切り、という見立てと整合する。
実測されたコンポーネント別の上限は次のとおり。
| コンポーネント | 上限 |
|---|---|
Git for Windowsのbash.exe -c | 8,203文字 |
同じMSYS2ランタイム上のecho.exe | 20,000文字は無傷で通過 |
ネイティブバイナリのpython.exe | 32,000文字は無傷で通過 |
WindowsのCreateProcess自体 | 32,767文字(コマンドライン全体、ドキュメント値) |
Windows自体はコマンドライン全体で32,767文字まで許容しているし、同じMSYS2ランタイム上の別バイナリでも20,000文字は通る。詰まっているのはbash -cだけ、という切り分けになっている。
実際の閾値が8,203文字よりずっと低く見える理由
Claude CodeのBashツールは、ユーザーが書いたコマンドをそのまま渡しているわけではない。実際にはこういう形でラップされている。
bash -c "source <shell snapshot> && export TEMP=... && shopt -u extglob
&& { unalias ...; } && eval '<ユーザーのコマンド>' && pwd -P >| <cwdファイル>"
このeval '...'ラッパーのせいで、ユーザーコマンド中のシングルクォート1文字が、エスケープ後は'"'"'という5文字に膨張する。アポストロフィを含む自然な文章(won'tや$(date '+%F')など)は、見た目のサイズより実際の消費バイト数がずっと大きい。加えてラッパー自体が約1KBを消費する。だから体感としては「8,000バイトを超えたあたりから失敗し始める」という報告になる。
症状2 — バックスラッシュが黙って消える(サイズ無関係)
こちらのほうが厄介だと報告者も書いている。エラーは一切出ないまま、ペアになったバックスラッシュだけが静かに半分になる。
\\→\になる\\\\→\\になる- Pythonの正規表現
r'\\d+'がr'\d+'として書き込まれ、「バックスラッシュにマッチする」が「数字にマッチする」に変わってしまう C:\Users\nameのような孤立したバックスラッシュは壊れずに残る
検証は295バイトという小さいペイロードでも再現している。つまりチャンクに分けて小さく送っても回避にならない、サイズに依存しないバグだということ。
| ペイロードサイズ | -c経由(現状) | 標準入力経由 |
|---|---|---|
| 295バイト | 壊れる(-4バイト) | 一致 |
| 1KB | 壊れる(-16バイト) | 一致 |
| 6.5KB | 壊れる(-100バイト) | 一致 |
| 15KB | 書き込み失敗 | 一致 |
| 52KB | WinError 206 | 一致 |
| 259KB | WinError 206 | 259KBまでバイト単位で一致 |
原因を掘り下げる — MSYS2のglob()が抱える固定8,192バイトのスタックバッファ
症状1の原因は、報告者いわく「ドキュメント化済み」とのこと。MSYS2ランタイムがWindowsのコマンドラインからargvを再構築する際、build_argv()(dcrt0.cc)がglob()を呼び出し、このglob()がUnicode変換のためにスタック上に確保した固定8,192バイトのバッファを使っているという、上流の既知issueが引用されている。
このglob()が呼ばれる条件は「コマンドライン中に?*[\"'(){}のいずれかの文字が含まれる場合」で、シングルクォートもダブルクォートも対象に入っている。つまりeval '...'ラッパーを使う通常のシェルコマンドは、ほぼ確実にこの経路を通る。
症状2(バックスラッシュの消失)については、報告者自身も「原因は推測であり未確認」と明言している。同じargv再構築処理がWindows流のエスケープ規則(バックスラッシュ+クォートをエスケープとして解釈する)を適用しているのではないか、というのが仮説。ただし測定結果自体(295バイトでも起きる、-s経由なら起きない)は再現性のある事実として提示されている。
参考として、OpenAIのCodex CLIでも同種の「Windowsで大きいペイロードをargv経由で渡すと壊れる」バグが報告されているとのこと。
何が効かないか — 手を出す前に知っておく
報告者が実際に試して効かなかった方法もまとめられている。同じ沼にはまらないための情報として先に共有しておく。
MSYS=noglob環境変数: -cの上限を8,203文字から32,731文字まで引き上げる効果は確かにある。ただしglobbing処理自体を無効化する副作用で、クォートを含むコマンドのargv再構築が別の形で壊れ、eval: line 1: unexpected EOF while looking for matchingという、結局同じ系統のエラーで死ぬ。常にeval '...'ラッパーを通るClaude Codeの実装とは相性が悪く、サイズ上限と引き換えに確実な破損を得るだけになる。
ペイロードをチャンクに分割する: 症状1(打ち切り)には有効でも、症状2(バックスラッシュ消失)は295バイトでも起きるため、分割しても意味がない。
Base64エンコードする: エンコード後のペイロードも結局argvを通るうえ、サイズが約33%増える分だけ状況が悪化する。
今すぐできる回避策
Claude Code本体側の修正を待たずに、手元でできる対処をまとめておく。
1. 大きいファイルはheredocでなくWriteツールに書かせるBashツールの-c経由の制約を受けるのはheredocやシェル一行コマンドの話で、Claude CodeのWrite/Editツールを使ったファイル書き込みはこの経路を通らない。8KBを超えるような設定ファイルやコード生成を頼むときは、「catでheredoc書き込みして」ではなく「Writeツールでファイルを作成して」と明示的に指示したほうが、この不具合を丸ごと避けられる。
1回あたりのeval後サイズが数千バイトに収まるよう、>>での追記を複数回に分ける。ただしこれは症状1(打ち切り)の緩和にしかならず、バックスラッシュを含む内容(正規表現・Windowsパスの二重エスケープなど)には症状2が別途乗ってくる点は変わらない。
正規表現パターンやエスケープが絡む文字列は、サイズに関係なく壊れる可能性がある。Pythonスクリプトやコード中の\\d+のような表現をClaude Codeに生成・書き込みさせる場合は、Bashのheredoc経由ではなくWrite/Editツール経由にしておくのが無難。
そもそもMSYS2/Git Bashを経由しない構成にすれば、このglob()のスタックバッファ制約自体を踏まなくなる。WSL2側には別種の重さ・権限プロンプトの課題があるので、そちらも合わせて把握したうえで選んだほうがいい。

似ているが原因が違う「unexpected EOF」もある
同じエラーメッセージで検索すると、今回の話とは無関係の原因がヒットすることがあるので、切り分けの参考に載せておく。報告者自身がIssue内で「関連するが原因は別」として整理していた。
- #26742(および重複の#26946、#27163): Windowsのユーザー名にアポストロフィが含まれるケース(
C:\Users\SeanO'Loughlinなど)。パス中のクォート文字が原因で、ペイロードサイズやargvとは無関係。#27163の報告者はmacOSでも同じエラーを再現しており、パス説を明示的に否定している - #48317、#29619: Markdownをheredocやghコマンドに渡す際のクォート処理に起因するもので、いずれも活動停止で自動クローズ済み
今回の#93915が他と違うのは、「ペイロードがbash -nの構文チェックを通る」「エラーの行番号がサイズに関わらず一定」という2点を実測で示している点。単に「heredocでエラーが出た」という報告よりも切り分けの精度が高い。
Git Bash自体の検出・パス設定でつまずいている場合は、これとは別の話になる。

現状のステータス(2026年9月13日時点)
このIssueは2026年9月12日に報告されたばかりで、bug・has repro・platform:windows・area:bashのラベルが付いているものの、Anthropic側からのコメントはまだ付いていない。直近のバージョン(v2.1.269、9月11日リリース)の変更点にも該当する修正は含まれていなかった。
報告者が提案している修正案は「コマンドをargvではなく標準入力(bash -s)経由、または一時ファイル経由で渡す」というシンプルなもので、これならglob()のスタックバッファを含むargv再構築処理自体を通らなくなる。実装コストの見立てとしても大きな話には見えないので、直る可能性は低くないと自分は見ているけど、現時点ではあくまで未修正の不具合として扱ったほうがいい。
Windows環境で日常的に大きいファイルをBash経由で生成している人は、claude --versionで自分のバージョンを確認しつつ、CHANGELOGを定期的にチェックしておくのがいい。
まとめ
Windows版Claude CodeでBashツールを使っていて、8KBを超えるheredoc書き込みがunexpected EOFで失敗する、あるいはコード中のバックスラッシュがこっそり消えるという症状に心当たりがあるなら、原因はコマンド内容の書き方ではなく、Git Bash(MSYS2)がargvを再構築する処理の実装上の制約にある可能性が高い。大きいファイルや正規表現・エスケープを含む内容は、Bashのheredoc経由ではなくWriteツール経由で書かせるのが、今のところ一番確実な回避策になる。
FAQ
macOSやWSL2でも同じ不具合は起きる?
報告されているのはGit for Windows(MSYS2ランタイム)特有のbash -c実装に起因する不具合。macOSのbashやWSL2内のLinuxネイティブbashはこの経路を通らないため、同じ症状にはならない。WSL2に切り替えれば根本的に回避できるが、WSL2固有の起動の重さや権限プロンプトの課題は別途ある。
8KBぎりぎりのコマンドなら大丈夫?
サイズだけを見て安全圏を判断するのは危険。eval '...'ラッパーの影響でシングルクォート1文字が5文字分に膨張するため、見た目のコマンド長より実際の消費バイト数はかなり大きくなる。さらにバックスラッシュの消失はサイズに関係なく発生するため、「短いから安全」とは言えない。
MSYS=noglobを設定すれば直る?
-cの文字数上限自体は8,203文字から32,731文字まで上がるが、globbing処理を無効化する副作用でクォートを含むコマンドのargv再構築が別の形で壊れる。Claude Codeは常にeval '...'ラッパーを使うため、この設定は逆効果になりやすい。報告者も「テスト済みで却下」としている。
この不具合はClaude Code固有の問題?
厳密には違う。bash自体(OS非依存)にもWindows自体(32,767文字まで許容)にも問題はなく、両者をつなぐMSYS2ランタイムのglob()実装が原因。ただし、この制約が実際に踏まれる状況を作っているのはClaude Codeが任意サイズのユーザーペイロードをargv経由で渡している設計であるため、「Claude Codeが原因で顕在化する」とは言える。