Claude Code

Claude Code hookでPythonスクリプトが黙って落ちる問題——パス・環境変数・作業ディレクトリの三重奏を全部解決した

Claude Code hookでPythonスクリプトが黙って落ちる問題——パス・環境変数・作業ディレクトリの三重奏を全部解決した

この記事は約 9 分で読めます

📖 目次
  1. 📌 何が起きたか
  2. 📌 環境
  3. 📌 詰まったポイント
  4. 📌 解決までの手順
  5. 📌 コード/設定の抜粋
  6. 📌 試してわかったこと
  7. 📌 まとめ

何が起きたか

Claude Code の hook 機能を使って、セッション開始時に Python スクリプトを自動起動しようとしたときの話。settings.jsonPreToolUse フックを書いて python3 myscript.py を呼んだら、コマンド自体は実行されているのにスクリプトが黙って落ちる。ログにも何も残らない。エラーも出ない。「あれ、動いてる?」状態が30分続いた。原因はパス・環境変数・作業ディレクトリの三重奏だった。

環境

  • macOS Sequoia 15.x / MacBook Pro M5 32GB
  • Claude Code(最新ビルド)
  • Python 3.12(Homebrew 管理)
  • 仮想環境: ~/Documents/AI_Automation_Base/.venv
  • 対象スクリプト: 01_Scripts/monitoring/session_start_hook.py(セッション開始時に Slack 通知 + ステータスチェックを走らせるやつ)
  • Ollama: localhost:11434 で稼働中

詰まったポイント

フックの書き方は公式ドキュメント通りに書いた。settings.json に以下を追加:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/Documents/AI_Automation_Base/01_Scripts/monitoring/session_start_hook.py"
          }
        ]
      }
    ]
  }
}

実行ログを見ると exit code: 0 で正常終了している。なのにスクリプトの出力が一切ない。print() すら出ない。

最初に疑ったのは「スクリプト自体が壊れてるか」だったが、ターミナルで直接 python3 01_Scripts/monitoring/session_start_hook.py を叩くと普通に動く。ここで「フック経由だと何かが違う」と気づいた。

次のエラー(推測ではなく実際に 2>&1 でキャプチャして確認した内容):

/usr/b​in/python3: No such file o​r directory

python3 の解決先が /usr/local/b​in/python3/opt/homebrew/b​in/python3 ではなく、フック実行時の PATH が削ぎ落とされた状態で /usr/b​in/python3 を探しに行っていた。macOS の /usr/b​in/python3 は Xcode Command Line Tools が入っていないと存在しない。

解決までの手順

ステップ1: フックの出力をまずキャプチャする

2>&1 をつけてエラーを拾えるようにする。出力先を一時ファイルに書き出した。

{
  "command": "python3 ~/Documents/AI_Automation_Base/01_Scripts/monitoring/session_start_hook.py >> /tmp/hook_debug.log 2>&1"
}

/tmp/hook_debug.log を見ると No such file o​r directory が出ていた。ここでようやく原因が「パス問題」と確定した。

ステップ2: フルパスで python3 を指定する

which python3 で確認してフルパスを直書きする。

which python3
# → /opt/homebrew/b​in/python3
{
  "command": "/opt/homebrew/b​in/python3 ~/Documents/AI_Automation_Base/01_Scripts/monitoring/session_start_hook.py >> /tmp/hook_debug.log 2>&1"
}

これで No such file o​r directory は消えた。が、次のエラーが来た。

ステップ3: 仮想環境の依存パッケージ問題

スクリプトが httpx をインポートしているが、システムの Python3 にはない。仮想環境に入れてあるやつを使わないといけない。

解決策は2つある。フックから bash -c 経由でシェルスクリプトを呼び、その中で source .venv/b​in/activate してから Python を起動する方法と、仮想環境の Python バイナリを直指定する方法。後者のほうが単純なので採用した。

{
  "command": "/Users/ichinosetaito/Documents/AI_Automation_Base/.venv/b​in/python3 /Users/ichinosetaito/Documents/AI_Automation_Base/01_Scripts/monitoring/session_start_hook.py >> /tmp/hook_debug.log 2>&1"
}

ステップ4: 作業ディレクトリの問題

スクリプト内で open("04_Config/.env") のような相対パスを使っていた部分が FileNotFoundError になった。フック実行時のカレントディレクトリは Claude Code がどこで起動したかによって変わる。絶対パスに直すか、スクリプト先頭で os.chdir() するかで対処。

import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
# これで __file__ の場所を基準に相対パスが使える

ステップ5: ANTHROPIC_API_KEY の漏れ問題

これが一番ハマった。フックから subprocess.run(["claude", "-p", ...]) を呼んでいる箇所があり、フック実行時の環境変数に ANTHROPIC_API_KEY が含まれていると API クレジットを消費しようとして残高不足エラーになる。Claude Code のサブスクリプション利用なのに API クレジットを食いに行く謎挙動。

# NG: 環境変数をそのまま渡す
subprocess.run(["claude", "-p", prompt])

# OK: ANTHROPIC_API_KEY を除外して渡す
env = {k: v for k, v in os.environ.items() if k != "ANTHROPIC_API_KEY"}
subprocess.run(["claude", "-p", prompt], env=env)

コード/設定の抜粋

最終的な settings.json のフック設定:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/Users/ichinosetaito/Documents/AI_Automation_Base/.venv/b​in/python3 /Users/ichinosetaito/Documents/AI_Automation_Base/01_Scripts/monitoring/session_start_hook.py >> /tmp/claude_hook.log 2>&1"
          }
        ]
      }
    ]
  }
}

スクリプト冒頭の定形処理:

import os
import sys

# 作業ディレクトリをスクリプトの場所に固定
os.chdir(os.path.dirname(os.path.abspath(__file__)))

# claude -p を使う場合は API キーを除外
def run_claude(prompt: str) -> str:
    env = {k: v for k, v in os.environ.items() if k != "ANTHROPIC_API_KEY"}
    result = subprocess.run(
        ["claude", "-p", prompt],
        capture_output=True, text=True, env=env
    )
    return result.stdout.strip()

デバッグ用のシェル:

#!/b​in/bash
# フックと同じ条件でスクリプトを手動実行してテストする
env -i HOME="$HOME" PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" \
  /Users/ichinosetaito/Documents/AI_Automation_Base/.venv/b​in/python3 \
  /Users/ichinosetaito/Documents/AI_Automation_Base/01_Scripts/monitoring/session_start_hook.py

env -i で環境変数をクリーンにした状態を再現できるので、「ターミナルでは動くのにフックで動かない」を手元で再現しやすくなる。

試してわかったこと

Claude Code のフックが実行されるプロセスは、ターミナルのシェルとは別の環境で動いている。PATH が削ぎ落とされているし、VIRTUAL_ENV も当然セットされていない。「ターミナルで動く = フックで動く」とはならない。

フックで何かを動かすときのチェックリストをまとめると:

  • python3 はフルパスか .venv/b​in/python3 直指定
  • スクリプト内の相対パスは os.chdir()pathlib.Path(__file__) 基準で管理
  • claude -p 呼び出しがあるなら ANTHROPIC_API_KEY を env から抜く
  • まず >> /tmp/debug.log 2>&1 でエラーをキャプチャしてから直す

フックはサイレントに失敗する。exit code: 0 が返ってきても「何もしなかった」だけで成功している可能性がある。ログ出力は必須。

まとめ

Claude Code hook の Python 起動は「PATH・仮想環境・作業ディレクトリ」の三つ全部を意識しないと黙って失敗する。デバッグは 2>&1 でログをキャプチャしてから始める。claude -p を呼ぶスクリプトは ANTHROPIC_API_KEY の除外を忘れずに。

👨‍💻
一ノ瀬泰斗
AI自動化エンジニア / Python個人開発者

Claude Code × Ollama × launchd で SNS・ブログ・KDPを全自動化。実測データと失敗談を軸に、月5万円収益化のリアルな記録を発信中。