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?

`/clear` でセッションIDが変わると、自作の安全 hook が黙って効かなくなる——SessionStart 前提の落とし穴と fail-closed の書き方

0
Posted at

Claude Code で安全のための hook(PreToolUse で危ない操作を止める類い)を書いている人向けの話です。/clear を使ったあとに、その hook が黙って効かなくなることがある、という落とし穴を、起票の報告と、hook の標準の仕組みから整理します。エラーが出ないので気づきにくく、気づいたときには「守っているつもりで何も守っていなかった」となるのが怖いところです。

結論から

  • /clear でセッションの文脈を捨てると、内部でセッションの ID(session_id)が新しく振り直される、という挙動が報告されています(anthropics/claude-code#70606)。
  • 一方で、起動時に一度だけ走る SessionStart の hook は、/clear のあとには再実行されません
  • そのため、「SessionStart でセッションごとの状態を用意し、PreToolUse でその状態を見て可否を判断する」作りの hook は、/clear のあとに「用意したはずの状態が見つからない」状態に陥ります。
  • このとき hook が「見つからない=該当なし=通す」と書いてあると、安全 hook が何も止めなくなります。 しかもエラーは出ません。

報告では、セッションごとのパスを検証する hook が、/clear のあとに三日間、気づかれないまま誤動作し続けたとされています。

何が起きているのか

Claude Code の hook には走る場面(イベント)があります。安全のための hook でよく使うのは次の二つです。

  • SessionStart:セッションが始まるときに一度だけ走る。一時ディレクトリを作る、認可の方針を読み込む、といった「準備」をここでやる。
  • PreToolUse:道具(ツール)を実行する前に毎回走る。Bash のコマンドやファイルの書き込みを見て、危なければ終了コード 2 で止める。

session_id は、どちらの hook に渡される入力にも含まれます。素直に設計すると、SessionStart

/tmp/claude-session-<session_id>/

のような場所に方針や記録を用意し、PreToolUse では渡ってきた session_id から同じ場所を組み立てて読む、という形になります。ここまでは自然です。

問題は /clear です。報告によれば、/clear のときに内部でセッションの ID が新しく振り直されます。すると、

  • /clear のあとの PreToolUse には、新しい session_id が渡される。
  • だが SessionStart再実行されない。新しい ID に対応する /tmp/claude-session-<新しいID>/ は作られていない。
  • PreToolUse の hook は、新しい ID から組み立てた場所を見にいくが、そこには何もない。

ここで hook の書き方によって結果が分かれます。

  • 状態が無いとき通す(fail open)と書いてある場合:すべての操作が素通りする。安全 hook が無効化されたのと同じ
  • 状態が無いとき止める(fail closed)と書いてある場合:すべて拒否される。作業は止まるが、危険は通さない(うるさいが安全側)。

加えて、CLAUDE_ENV_FILE に書いた環境変数は hook の子プロセスには渡らないため、hook 側で「新しい ID を古い ID に読み替える」といった復旧も素直にはできない、という点も報告で挙げられています。

いちばんの怖さは「無症状」であること

/clear は文脈を整理するために日常的に押す操作です。押したあとも画面は普通に動きます。hook は終了コード 0(問題なし)を返して静かに通すので、ログにも異常は残りません。守りが外れたこと自体が無症状で、次に本当に危ない操作が来たとき、しかも止まらずに通ってしまったあとで気づきます。

自分の hook が該当するか

次のすべてに当てはまると、射程に入っています。

  • SessionStart で、一時ディレクトリ・ロックファイル・認可の方針など、セッションごとの状態を用意している。
  • PreToolUse で、その状態を見て可否を判断している(とくに session_id からパスを組み立てて読んでいる)。
  • セッション中に /clear を使うことがある。

逆に、渡ってきたコマンドやファイルのパスだけを見て、その場で可否を決める「状態を持たない」hook は影響を受けません。 tool_input を読んでその場で判断する作りなら、session_id が変わっても挙動は変わりません。まず確認すべきは、自分の安全 hook が状態を持っているかどうかです。

直し方——守る hook は「迷ったら止める」側に倒す

提供側の対応(/clear のあとに SessionStart を呼び直す等)を待つ前に、書く側で自衛できます。原則は一つ、安全のための hook は、判断に必要な状態が見つからないとき、通す側でなく止める側に倒す(fail closed)

1. 状態が無いなら拒否する(fail closed)

#!/bin/bash
# 状態が無いときは通さず止める(matcher は "Bash" など)
INPUT=$(cat)
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty')
STATE_DIR="/tmp/claude-session-${SID}"

# SessionStart が用意したはずの状態が無い = /clear 後などで前提が崩れている
if [ ! -d "$STATE_DIR" ]; then
  echo "止めました: セッションの安全の状態が見つかりません(/clear 後の可能性)。" >&2
  echo "新しいセッションを開き直すか、SessionStart の準備をやり直してください。" >&2
  exit 2
fi

# ここから先は通常の検証...
exit 0

うるさく感じても、守りが黙って外れて危険な操作を通すより、止まって気づける方が安全です。

2. PreToolUse の側で、状態が無ければその場で作り直す(遅延初期化)

止まるのが業務上つらいなら、SessionStart に頼りきらず、PreToolUse で「無ければ作る」ようにします。

if [ ! -d "$STATE_DIR" ]; then
  mkdir -p "$STATE_DIR"
  printf 'deny\n' > "$STATE_DIR/policy"   # 既定は必ず安全側(無ければ拒否)に
fi

作り直しの既定値を「全部許可」にすると fail open に逆戻りするので、必ず安全側にします。

3. そもそも状態を持たない hook を選ぶ

いちばん壊れにくいのは、セッションの状態に依存しない hook です。可否の判断が tool_input(コマンドの文字列、ファイルのパス)だけで完結するなら、session_id が変わっても何も起きません。新しく hook を書くときは、状態をまたぐ必要が本当にあるかをまず疑ってください。

まとめ

/clear はセッションの ID を振り直すが、SessionStart は再実行されない。だからセッションの状態を前提にする安全 hook は、/clear のあとに黙って効かなくなることがある——しかもエラーが出ないので気づけない。守りの hook は、状態が見つからないとき通さず止める(fail closed)か、その場で安全側に作り直す。いちばん確実なのは、状態を持たず tool_input だけで判断する hook を選ぶことです。


無料で公開している安全 hook 集 cc-safe-setup の hook は、多くが tool_input を見てその場で判断する「状態を持たない」作りなので、この /clear の問題の影響を受けません(危ない Bash、ファイルの全置換、git の破壊的操作などを実行の前で止めます)。まず自分の環境に入れて土台を固める用途に使えます。

「どういう事故が、どの設定で防げるのか」を、実際に起きた起票から逆算して順にまとめた買い切りの本も出しています(Claude Code 事故防止の本・¥800・第3章まで無料・全51章)。この /clear で安全 hook が黙って効かなくなる話は、その第51章として収録しています。Kindle 版(Kindle Unlimited 対応)でも読めます。

本記事のうち、/clear がセッションの ID を振り直し SessionStart が再実行されないという挙動は、起票 #70606 の報告とコメントに基づくものです(筆者自身が引き起こした事故ではありません)。session_id が hook の入力に含まれること、PreToolUse が終了コード 2 で操作を止められること、SessionStart が準備のためのイベントであることは、hook の標準の仕組みに基づいて書いています。

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?