🔬

Claude Code 実践検証 Day 10|Headless検証結果:Hooks無効、終了コード常に0の衝撃

に公開

https://zenn.dev/akira_cloudjob/articles/ee7987b92d5ac4
こちらで、別の結果になりました。


昨日の予告通り、今日はHeadlessモードの検証結果をまとめます。


検証結果サマリー

全検証項目の結論を表にまとめました。

検証項目 結果 実務への影響
CLAUDE.md読み込み ✅ 有効 そのまま使える
permissions設定 ✅ 有効 allow/denyともに機能
Git Bash日本語 ✅ 正常 推奨環境
PowerShell日本語 ❌ 文字化け 非推奨

予想と違った点が2つ。どちらも自動化スクリプトの設計に影響する重要な発見でした。


検証3:Windows環境でのシェル比較

検証内容

同じプロンプトをGit BashとPowerShellから実行し、日本語出力を比較しました。

claude -p "「こんにちは」と返答してください"

結果

シェル 日本語出力 特殊文字
Git Bash ✅ 正常表示 ✅ ①②③★☆も正常
PowerShell ❌ 文字化け ❌ 記号も化ける

PowerShellでの出力例(文字化け):

縺薙s縺ォ縺。縺ッ

Git Bashでの出力例(正常):

こんにちは

PowerShellでの回避策

UTF-8設定を入れても改善しませんでした。

# これでも改善しない
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$env:PYTHONUTF8 = "1"

Claude Code側のstdout処理がGit Bashを前提にしているっぽいですね。

結論

WindowsでHeadlessモードを使うなら、Git Bash一択です。

バッチファイルやPowerShellスクリプトから呼び出す場合も、Git Bash経由で実行することをお勧めします。

# PowerShellからGit Bash経由で実行
& "C:\Program Files\Git\bin\bash.exe" -c 'claude -p "日本語プロンプト"'

検証4:設定ファイルの読み込み

検証内容

CLAUDE.mdとsettings.local.jsonの読み込みを確認しました。

CLAUDE.mdに以下のルールを設定:

# ルール
- 応答は必ず「検証完了:」で始めること

結果

$ claude -p "テスト"
検証完了:テストの応答です。

Headlessモードでも、CLAUDE.mdのルールがちゃんと適用されました。

permissions設定も同様に機能します。

{
  "permissions": {
    "deny":  ["Bash(rm:*)", "Bash(del:*)"]
  }
}

この設定でrmやdelコマンドの使用を禁止すると、Headlessモードでも拒否されます。


検証5:出力形式とコスト取得

JSON出力の内容

--output-format jsonで、以下の情報が取得できます。

{
  "type": "result",
  "subtype": "success",
  "total_cost_usd": 0.012766,
  "duration_ms": 3683,
  "num_turns": 1,
  "session_id": "xxx-xxx-xxx",
  "modelUsage": {
    "claude-sonnet-4-20250514": {
      "inputTokens": 1234,
      "outputTokens": 567,
      "cacheCreationInputTokens": 0,
      "cacheReadInputTokens": 0
    }
  }
}

取得できる情報

項目 説明 活用例
total_cost_usd 実行コスト(ドル) コスト管理・予算監視
duration_ms 実行時間(ミリ秒) パフォーマンス計測
num_turns ターン数 タスク複雑度の把握
subtype 結果種別 エラー判定

コスト情報が取れるのは嬉しいですね。月間の利用状況をログに残せます。


検証6:タイムアウト制御

検証内容

長時間実行のハング回避方法を検証しました。

結果

方法 効果
--max-turns N ターン数で制限(推奨)
timeout 30s claude -p ... 時間で強制終了(補助)
BASH_DEFAULT_TIMEOUT_MS ❌ Bashツール内部のみ

--max-turnstimeoutコマンドの併用がベストプラクティスです。

timeout 120s claude -p "複雑なタスク" --max-turns 10

注意点として、--max-turns 0は「無制限」として扱われます。0を指定しても制限されないので、必ず1以上の値を設定してください。


公式 vs 実態

項目 公式の印象 実際の動作
設定ファイル読み込み 対話モードと同じ ✅ 概ね同じ
Hooks発火 発火する? ❌ 発火しない
終了コード エラー時は非0? ❌ 常に0
Windows対応 問題なし? ⚠️ Git Bash推奨

公式ドキュメントには「Headlessモード固有の制約」が明記されていません。この辺、今後のドキュメント改善に期待したいところです。


実務での推奨パターン

パターン1:複数ファイルの一括処理

たとえば、ディレクトリ内の全ソースファイルにコメントを追加したい場合。

#!/bin/bash
for file in src/*.ts; do
  echo "Processing: $file"
  result=$(timeout 60s claude -p "このファイルの各関数に日本語でJSDocコメントを追加してください: $(cat $file)" \
    --output-format json \
    --max-turns 3 \
    2>&1) || true

  # エラー判定
  if echo "$result" | jq -e '.subtype | startswith("error")' > /dev/null 2>&1; then
    echo "Error: $file"
    continue
  fi

  echo "Done: $file"
done

パターン2:定型レビュースクリプト

コードレビューを定型化したい場合の例。

#!/bin/bash
# review.sh - 簡易コードレビュー

target=${1:-.}
result=$(claude -p "以下のコードをレビューしてください。セキュリティ、パフォーマンス、可読性の観点で問題点を指摘してください。$(find $target -name '*.ts' -exec cat {} \;)" \
  --output-format json \
  --max-turns 5)

# 結果をMarkdownで保存
echo "$result" | jq -r '.result.content // .message' > review-result.md
echo "Review saved to review-result.md"

パターン3:実行ログの取得

Hooksが使えないので、出力のリダイレクトでログを取ります。

claude -p "$PROMPT" --output-format json 2>&1 | tee execution.log

# コスト確認
cat execution.log | jq '.total_cost_usd'

避けるべきパターン

# NG:終了コードに依存
claude -p "test" || exit 1  # 常に成功扱いになる

# NG:PowerShellでの直接実行(日本語環境)
claude -p "日本語プロンプト"  # 文字化けの可能性

# NG:Hooksでのログ取得を期待
# → Headlessでは発火しない

まとめ

今回の検証で明らかになった重要なポイントは1つ。

  1. WindowsはGit Bash一択。PowerShellでは日本語が文字化けする

自動化スクリプトを書く際は、これらの制約を織り込んだ設計が必要です。特に終了コードの件は、既存のシェルスクリプトの書き方を変える必要があるかもしれません。

明日からは新しいテーマ「Skills」に入ります。model-invokedの発火条件、これも公式ドキュメントだけでは分からないことが多いんですよね。


シリーズ情報

  • シリーズ名:Claude Code 実践検証ガイド 2025
  • 著者:Akira
  • 検証環境:Windows 11 / Git Bash / Claude Code v2.0.60

次回:Day 11「Skillsの設計と自動起動条件」

Discussion