お疲れ様です。tkmiです。
先日、Windows環境でgit pullが失敗しました。エラーはこんな内容です。
error: invalid path 'docs/reviews/request/CON.md'
「???」となりました。ファイル名が原因でgitが失敗するなんて、想像もしていませんでした。
調べてみると、Windows特有の落とし穴でした。しかも辿っていくとDOS時代まで遡る話で、なかなか奥深かったので記事にします。
原因はWindowsの予約デバイス名
調べてわかったのは、Windowsには「予約デバイス名」というものが存在するということでした。ファイル名として使えない名前のことです。
代表的なものには、次のような名前があります。
CON, PRN, AUX, NUL, COM1〜COM9, LPT1〜LPT9
今回引っかかったのは CON.md というファイルです。
一見普通のMarkdownファイルですが、拡張子を付けても回避できません。CON.md も NUL.txt も、Windowsからすればアウトです。大文字小文字も区別されません。con.md でも Con.md でも同じく引っかかります。
この予約名は、ファイル名だけでなくディレクトリ名にも使えません。パス全体というより、パスを構成する一つひとつの名前(ファイル名・ディレクトリ名)が判定対象です。
今回は末尾のCON.mdがこれに該当していました。同じディレクトリの他のファイル(例:ACC.md)は普通に展開できたので、最初は原因の見当がつきませんでした。
DOS由来の名前が今も残っている
一番「へー」となったのが、この予約デバイス名の由来です。
COM1 や LPT1 は、実際にシリアルポートやプリンタポートに対応する「デバイス名」だったそうです。CONはコンソール(キーボードと画面)、NULは書き込んだ内容を破棄する特殊ファイルのことです。
DOS時代、デバイスをファイルのように扱う仕組みがありました。その名前との互換性が、令和のWindowsにも引き継がれているということでした。
Microsoftの公式ドキュメント「Naming Files, Paths, and Namespaces」にも、これらの名前は予約済みだと書かれています。
この地雷を踏んでいるのは自分だけではなさそうです。Viteがビルド時にdist/nulというファイルを生成してしまい、同じ invalid path エラーに遭遇した、という事例も見つかりました。
長年、いろいろな人がいろいろな場面で踏み続けている落とし穴のようです。
なぜGitでinvalid pathになるのか
もう一つ気になったのが、「この予約デバイス名がすべてのgit環境で問題になるわけではなさそう」という点でした。
調べてみると、Git for Windowsには、Windows上で問題になるパスを拒否するための独自のチェックがあります。予約デバイス名は、このチェックの中で判定されているようです。Gitのソースコードcompat/mingw.cでも確認できます。
これはWindows版git特有の仕組みで、Linux版のgitには同じ制約がありません。
なお、Windows版gitにはcore.protectNTFSという設定もあり、こちらもデフォルトで有効です。ただし、これはNTFS上で問題になるパスを防ぐための設定で、今回の予約デバイス名のチェックとは別の仕組みです。
こうしたWindows側だけのチェックがあるため、ファイルが作られた時点では誰も気づけませんでした。Windows側でcheckoutして初めて、git pullやブランチ作成ができなくなります。通常のWindows環境ではそのまま扱えません。
他にWindowsでcheckoutしているメンバーがいれば、理屈上は同じことが起きるはずです。ただ、実際に他のメンバーで確認したわけではなく、これは推測にとどまります。
ファイルがどの環境で作られたかも、明確に検証したわけではありません。Linux環境など、Windowsの予約名の制約を受けない環境から追加された可能性はありそうです。
切り分けの手順
実際にどう切り分けたかをまとめておきます。
- 単体チェックアウトでの再現確認
CON.mdだけを対象にcheckoutを試し、単体でもinvalid pathが再現することを確認 - 同じディレクトリにある他のファイルを確認
同じディレクトリのACC.mdなどが正常に展開できることを確認し、ディレクトリ名(日本語を含む)自体は無関係だと切り分け - リネーム後の動作確認
同じ内容のファイルをCON-review.mdのような名前に変えると、Windows版gitで正常に展開でき、内容も問題なく読めることを確認 - リポジトリ全体のスキャン
予約デバイス名やWindowsで不正な文字を含むパスがないか全体を走査し、該当はCON.mdの1件のみであることを確認
対応
今回、実際に確認できた確実な解決方法は、CON.mdを予約デバイス名に該当しないファイル名へリネームすることでした。
- CON.mdをリネーム(内容は一切変更なし)
- 索引ファイル内の該当リンクを新しいファイル名に追随修正
恒久対応としては、予約名を含まないファイル名に変更するのが確実です。
ちなみに今回は、機能名の頭文字を3文字取ってファイル名のプレフィックスにする、という命名規則が使われていたようです。
たまたま対象の機能名の頭3文字がCONと一致してしまったのではと推測しています。3文字プレフィックス自体はよくある命名パターンだと思うので、同じ規則を使っていれば誰でも踏みうる地雷だと思います。
まとめ
- Windowsには予約デバイス名があり、代表的なものにCON、PRN、AUX、NUL、COM1〜9、LPT1〜9がある
- 予約デバイス名は、拡張子を付けても回避できず、ファイル名だけでなくディレクトリ名にも使えない
- Git for Windowsには、Windowsで問題になるパスを拒否する独自のチェックがあり、CON.mdもそこで拒否される
- core.protectNTFSもWindows版gitで有効な保護設定だが、今回の予約デバイス名チェックとは別の仕組み
- 機能名の頭文字を取るような命名規則では、意図せず予約名と一致することがある
- 確実な解決策は、予約名を含まないファイル名へのリネーム
3文字プレフィックスのような命名規則を使っている場合は、Windowsの予約デバイス名との衝突だけは頭の片隅に置いておくと安心です。
参考

