VS Code の terminal からではなくGUI上で commit すると conda 環境の pre-commit が失敗した話
はじめに
Python プロジェクトで pre-commit を使い、commit 時に linter を実行する設定を入れていました。
普段は VS Code の統合ターミナル上で conda 環境を有効化して開発しており、必要なライブラリもその環境内にインストール済みでした。
そのため、VS Code 左側の GUI上(以降Source Controlとする) から commit しても同じように動くと思っていました。
しかし、ターミナルから git commit した場合は成功するのに、VS Code の Source Control から commit した場合だけ pre-commit が失敗しました。
発生した事象
conda 環境を有効化した状態で開発していました。
conda activate sample-env
その環境内に、linter が依存するライブラリをインストールしていました。
pip install xxx
この状態で、ターミナルから commit すると問題なく成功します。
git commit
一方で、VS Code の Source Control から commit すると、pre-commit の hook 内で linter が失敗しました。
ModuleNotFoundError: No module named 'xxx'
つまり、同じリポジトリ・同じ設定にもかかわらず、commit の実行経路によって結果が変わる状態でした。
前提
今回の pre-commit 設定は、概ね以下のような構成でした。
repos:
- repo: local
hooks:
- id: lint
name: lint
entry: python -m flake8
language: system
types: [python]
ポイントは language: system を使っている点です。
language: system の場合、pre-commit は hook 用の独立した仮想環境を作らず、実行時の環境にあるコマンドを利用します。
そのため、entry に書いた python がどの Python を指すかは、実行時の PATH に依存します。
原因
原因は、VS Code の Source Control から実行される Git 操作が、統合ターミナルで activate している conda 環境と同じ実行環境で動くとは限らないことでした。
ターミナルで以下を実行すると、
conda activate sample-env
そのターミナルセッションでは conda 環境側の Python が優先されます。
そのため、ターミナルから git commit した場合、hook 内の python -m flake8 も conda 環境の Python で実行され、必要なライブラリを参照できました。
一方で、VS Code の Source Control から commit した場合、Git は VS Code 側から実行されます。
このとき、統合ターミナルで有効化した conda 環境の状態が、そのまま Git hook の実行環境に反映されるとは限りません。
結果として、hook 内の python が conda 環境ではない Python を指し、conda 環境内に存在するライブラリを見つけられず失敗しました。
確認方法
この手の問題では、hook 内で実際にどの Python が使われているかを確認すると切り分けしやすいです。
一時的に以下のような hook を追加します。
repos:
- repo: local
hooks:
- id: debug-python
name: debug python
entry: python -c "import sys; print(sys.executable); print(sys.path)"
language: system
pass_filenames: false
そのうえで、以下を比較します。
- ターミナルから
git commit - VS Code の Source Control から commit
出力される sys.executable が異なる場合、commit の実行経路によって参照している Python が違うと判断できます。
対処法
conda 環境を明示して実行する
conda run を使うことで、hook 内で使用する conda 環境を明示できます。
repos:
- repo: local
hooks:
- id: lint
name: lint
entry: conda run -n sample-env python -m flake8
language: system
types: [python]
これにより、PATH の状態に依存せず、指定した conda 環境で linter を実行できます。
ただし、開発者ごとに conda 環境名が異なる場合は、この方法も環境依存になります。
pre-commit 側で環境を管理する
より再現性を重視するなら、language: system に依存せず、pre-commit 側に hook 環境を管理させる方法があります。
例えば flake8 であれば、以下のように設定できます。
repos:
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
追加の plugin が必要な場合は、additional_dependencies に定義できます。
repos:
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
この構成であれば、現在 activate している conda 環境への依存を減らせます。
まとめ
今回の問題は、VS Code の Source Control が悪いというより、pre-commit の設定が実行時環境に依存していたことが原因でした。
特に、以下の条件が揃うと発生しやすいです。
- conda 環境を使っている
-
pre-commitで linter を実行している -
language: systemを使っている -
entryにpythonなど PATH 依存のコマンドを書いている - ターミナルからの
git commitは成功する - VS Code の Source Control からの commit だけ失敗する
VS Code の統合ターミナルで conda 環境を有効化していても、Source Control から実行される Git hook が同じ環境で動くとは限りません。
pre-commit を使う場合は、「自分のターミナルで動くか」だけでなく、「どの経路で commit しても同じように動くか」まで確認した方が安全だと感じました。