0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

"Claude Codeのhookをテストする方法——13,931テストから学んだ品質保証"

0
Posted at

Claude Codeのhookは、危険なコマンドをブロックするセーフティネットだ。しかし、テストされていないhookはセーフティネットに穴が空いているのと同じだ。
rm -rf / をブロックする」hookを作ったとする。本当にブロックされるか? rm -rf /(スペース複数)でもブロックされるか? sudo rm -rf /home は? rm -rf node_modules(安全な削除)まで巻き込んでいないか?
cc-safe-setupでは605個のhookに対して13,931のテストを書いている。この記事では、そのテスト手法を解説する。
hookの入出力は単純だ。標準入力でJSON(ツール呼び出しの情報)を受け取り、exit codeで結果を返す。

  • exit 0: 許可(hookを通過)
  • exit 2: ブロック(ツール呼び出しを中止)
    テストの基本形:
echo '{"tool_input":{"command":"rm -rf /"}}' | bash hook.sh
echo $?  # → 2(ブロック)
echo '{"tool_input":{"command":"ls -la"}}' | bash hook.sh
echo $?  # → 0(許可)

これだけだ。hookの入力形式はClaude Codeが送るJSON、出力はexit code。特別なテストフレームワークは不要で、bashだけで完結する。
テストを量産するために、ヘルパー関数を作る。cc-safe-setupの test.sh で実際に使っている形式がこれだ:

PASS=0
FAIL=0
test_hook() {
    local name="$1" input="$2" expected_exit="$3" desc="$4"
    local actual_exit=0
    echo "$input" | bash "/tmp/test-$name.sh" > /dev/null 2>/dev/null || actual_exit=$?
    if [ "$actual_exit" -eq "$expected_exit" ]; then
        echo "  PASS: $desc"
        PASS=$((PASS + 1))
    else
        echo "  FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
        FAIL=$((FAIL + 1))
    fi
}

使い方:

test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf /"}}' 2 "rm -rf / blocked"
test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf node_modules"}}' 0 "rm -rf node_modules allowed"
test_hook "destructive-guard" '{"tool_input":{"command":"git reset --hard"}}' 2 "git reset --hard blocked"
test_hook "destructive-guard" '{"tool_input":{"command":"git reset --soft HEAD~1"}}' 0 "git reset --soft allowed"

当然のテスト。危険なコマンドが正しくexit 2を返すことを確認する。

test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf /"}}' 2 "rm -rf / blocked"
test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf ~/"}}' 2 "rm -rf ~/ blocked"
test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf ../"}}' 2 "rm -rf ../ blocked"
test_hook "destructive-guard" '{"tool_input":{"command":"sudo rm -rf /home"}}' 2 "sudo rm -rf blocked"

hookが厳しすぎると、正常な操作までブロックしてしまう。これが偽陽性だ。

test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf node_modules"}}' 0 "rm -rf node_modules allowed"
test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf dist"}}' 0 "rm -rf dist allowed"
test_hook "destructive-guard" '{"tool_input":{"command":"rm -rf .cache"}}' 0 "rm -rf .cache allowed"
test_hook "destructive-guard" '{"tool_input":{"command":"echo git reset --hard"}}' 0 "echo内のコマンドは通す"

echo git reset --hard が通ることの確認は重要だ。文字列中に危険なパターンが含まれているだけでブロックすると、ログ出力やコメントにも反応してしまう。
想定外の入力でhookがクラッシュしないことを確認する。

test_hook "destructive-guard" '' 0 "empty input passes"
test_hook "destructive-guard" '{"tool_input":{}}' 0 "missing command field passes"
test_hook "destructive-guard" 'not json' 0 "invalid JSON passes"
test_hook "destructive-guard" '{"tool_input":{"command":"rm    -rf    /"}}' 2 "multi-space rm -rf blocked"

エッジケースのテストは地味だが、実運用で最も効果がある。Claude Codeが送るJSONの形式は常に一定とは限らない。
npx cc-safe-setup --verify で、インストール済みの全hookに対してテストを自動実行できる。

$ npx cc-safe-setup --verify
Running hook verification...
destructive-guard: 33/33 passed
branch-guard: 12/12 passed
secret-guard: 8/8 passed
...
All hooks verified.

GitHub Actionsでhookテストを自動実行する設定:

name: Hook Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: bash test.sh

cc-safe-setupの13,931テストは全てこのパイプラインで実行されている。PRごとにテストが通ることを確認してからマージする。
13,931テストという数字は結果であって目標ではない。hookが1つ増えるたびに「ブロックすべきもの」「通すべきもの」「エッジケース」の3観点でテストを追加する。その積み重ねが6,000になった。
テストの目的は、hookが正しく動くことの証明だ。「5,000テストあります」ではなく「destructive-guardは33パターン全てでブロックと許可が正しく動きます」と言えることが大事だ。
🛡 ワンコマンドで安全設定: npx cc-safe-setup — 634個のhook例を収録。13,931テストで検証済み。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?